onboarding: Adjust the AI upsell card depending on user's state (#35658)
Use includes centralizing what each plan delivers in one single file (`plan_definitions.rs`). Release Notes: - N/A
This commit is contained in:
parent
0025019db4
commit
30414d154e
11 changed files with 398 additions and 284 deletions
1
assets/images/certified_user_stamp.svg
Normal file
1
assets/images/certified_user_stamp.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.4 KiB |
1
assets/images/pro_trial_stamp.svg
Normal file
1
assets/images/pro_trial_stamp.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="51" height="51" fill="none"><path fill="#000" fill-opacity=".15" d="M45 3a3 3 0 0 1 3 3v39a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h39ZM10 7a3 3 0 0 0-3 3v31a3 3 0 0 0 3 3h31a3 3 0 0 0 3-3V10a3 3 0 0 0-3-3H10Z"/><rect width="36" height="36" x="7.5" y="7.5" stroke="#000" stroke-dasharray="2 2" rx=".5"/><rect width="44" height="44" x="3.5" y="3.5" stroke="#000" stroke-dasharray="2 2" rx="2.5"/><path fill="#000" d="M28.636 13.124c.732 0 1.27.617 1.27 1.464s-.538 1.465-1.27 1.465-1.27-.618-1.27-1.465c0-.847.538-1.464 1.27-1.464Zm-6.066-.784c.69 0 1.171.465 1.171 1.124s-.48 1.124-1.17 1.124h-.66V16h-.653v-3.66h1.312Zm3.323.8c.46 0 .784.319.837.82h-.649c-.042-.166-.188-.27-.376-.27-.319 0-.544.297-.544.73V16h-.627v-2.823h.6v.382h.022a.844.844 0 0 1 .737-.418Zm2.743.56c-.382 0-.643.36-.643.888s.261.89.643.89c.381 0 .643-.362.643-.89s-.262-.889-.643-.889Zm-6.725.314h.686c.308 0 .517-.22.517-.55 0-.33-.21-.549-.517-.549h-.686v1.099ZM28.63 36.124c.649 0 1.083.366 1.083.91v.957c0 .382.036.721.104 1.009h-.606a2.793 2.793 0 0 1-.078-.366h-.027c-.151.266-.413.419-.757.419-.539 0-.915-.356-.915-.868 0-.523.397-.811 1.228-.884l.173-.016c.177-.016.25-.09.25-.246 0-.24-.172-.392-.465-.392-.267 0-.46.126-.48.314h-.613c.006-.486.466-.837 1.104-.837Zm3.413 1.867c0 .355.167.527.512.528.146 0 .303-.031.455-.1v.523c-.157.074-.361.11-.575.11-.67 0-1.02-.366-1.02-1.06v-2.286h-.81v-.575h1.438v2.86Zm-11.452-2.076h-1.03V39h-.654v-3.085h-1.035v-.575h2.719v.575Zm2.23.226c.46 0 .784.318.836.82h-.648c-.042-.167-.188-.271-.376-.271-.32 0-.544.297-.544.73V39h-.627v-2.823h.6v.382h.022a.844.844 0 0 1 .737-.418Zm2.993 2.284h.785V39h-2.196v-.575h.785v-1.673h-.758v-.575h1.384v2.248Zm3.272-.784a.583.583 0 0 1-.204.062l-.168.022c-.46.057-.653.193-.653.449 0 .23.162.382.418.382.34 0 .607-.272.607-.622v-.293Zm-3.638-2.782c.267 0 .477.21.477.476a.477.477 0 0 1-.952 0c0-.267.214-.475.475-.476Z"/><path fill="#000" fill-rule="evenodd" d="M20.219 19.813a.406.406 0 0 0-.407.406v8.937H19V20.22c0-.673.546-1.219 1.219-1.219h10.884a.61.61 0 0 1 .431 1.04l-6.704 6.704h1.889v-.838h.812v1.041a.61.61 0 0 1-.61.61h-2.903l-1.397 1.396h6.332v-5.078h.813v5.078a.812.812 0 0 1-.813.813H21.81l-1.422 1.422h10.394a.406.406 0 0 0 .407-.407v-8.937H32v8.937c0 .673-.546 1.219-1.219 1.219H19.897a.61.61 0 0 1-.431-1.04l6.678-6.679h-1.863v.813h-.812v-1.016a.61.61 0 0 1 .61-.61h2.878l1.422-1.421h-6.332v5.078h-.813v-5.078c0-.449.364-.813.813-.813h7.144l1.422-1.422H20.219Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -1,9 +1,9 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ai_onboarding::{AgentPanelOnboardingCard, BulletItem};
|
use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions};
|
||||||
use client::zed_urls;
|
use client::zed_urls;
|
||||||
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
||||||
use ui::{Divider, List, Tooltip, prelude::*};
|
use ui::{Divider, Tooltip, prelude::*};
|
||||||
|
|
||||||
#[derive(IntoElement, RegisterComponent)]
|
#[derive(IntoElement, RegisterComponent)]
|
||||||
pub struct EndTrialUpsell {
|
pub struct EndTrialUpsell {
|
||||||
|
@ -18,6 +18,8 @@ impl EndTrialUpsell {
|
||||||
|
|
||||||
impl RenderOnce for EndTrialUpsell {
|
impl RenderOnce for EndTrialUpsell {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let plan_definitions = PlanDefinitions;
|
||||||
|
|
||||||
let pro_section = v_flex()
|
let pro_section = v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
|
@ -31,13 +33,7 @@ impl RenderOnce for EndTrialUpsell {
|
||||||
)
|
)
|
||||||
.child(Divider::horizontal()),
|
.child(Divider::horizontal()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(plan_definitions.pro_plan(false))
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("500 prompts with Claude models"))
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"Unlimited edit predictions with Zeta, our open-source model",
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
Button::new("cta-button", "Upgrade to Zed Pro")
|
Button::new("cta-button", "Upgrade to Zed Pro")
|
||||||
.full_width()
|
.full_width()
|
||||||
|
@ -68,11 +64,7 @@ impl RenderOnce for EndTrialUpsell {
|
||||||
)
|
)
|
||||||
.child(Divider::horizontal()),
|
.child(Divider::horizontal()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(plan_definitions.free_plan());
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("50 prompts with the Claude models"))
|
|
||||||
.child(BulletItem::new("2,000 accepted edit predictions")),
|
|
||||||
);
|
|
||||||
|
|
||||||
AgentPanelOnboardingCard::new()
|
AgentPanelOnboardingCard::new()
|
||||||
.child(Headline::new("Your Zed Pro Trial has expired"))
|
.child(Headline::new("Your Zed Pro Trial has expired"))
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use gpui::{Action, IntoElement, ParentElement, RenderOnce, point};
|
use gpui::{Action, IntoElement, ParentElement, RenderOnce, point};
|
||||||
use language_model::{LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
|
use language_model::{LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
|
||||||
use ui::{Divider, List, prelude::*};
|
use ui::{Divider, List, ListBulletItem, prelude::*};
|
||||||
|
|
||||||
use crate::BulletItem;
|
|
||||||
|
|
||||||
pub struct ApiKeysWithProviders {
|
pub struct ApiKeysWithProviders {
|
||||||
configured_providers: Vec<(IconName, SharedString)>,
|
configured_providers: Vec<(IconName, SharedString)>,
|
||||||
|
@ -128,7 +126,7 @@ impl RenderOnce for ApiKeysWithoutProviders {
|
||||||
)
|
)
|
||||||
.child(Divider::horizontal()),
|
.child(Divider::horizontal()),
|
||||||
)
|
)
|
||||||
.child(List::new().child(BulletItem::new(
|
.child(List::new().child(ListBulletItem::new(
|
||||||
"Add your own keys to use AI without signing in.",
|
"Add your own keys to use AI without signing in.",
|
||||||
)))
|
)))
|
||||||
.child(
|
.child(
|
||||||
|
|
|
@ -3,6 +3,7 @@ mod agent_panel_onboarding_card;
|
||||||
mod agent_panel_onboarding_content;
|
mod agent_panel_onboarding_content;
|
||||||
mod ai_upsell_card;
|
mod ai_upsell_card;
|
||||||
mod edit_prediction_onboarding_content;
|
mod edit_prediction_onboarding_content;
|
||||||
|
mod plan_definitions;
|
||||||
mod young_account_banner;
|
mod young_account_banner;
|
||||||
|
|
||||||
pub use agent_api_keys_onboarding::{ApiKeysWithProviders, ApiKeysWithoutProviders};
|
pub use agent_api_keys_onboarding::{ApiKeysWithProviders, ApiKeysWithoutProviders};
|
||||||
|
@ -11,51 +12,14 @@ pub use agent_panel_onboarding_content::AgentPanelOnboarding;
|
||||||
pub use ai_upsell_card::AiUpsellCard;
|
pub use ai_upsell_card::AiUpsellCard;
|
||||||
use cloud_llm_client::Plan;
|
use cloud_llm_client::Plan;
|
||||||
pub use edit_prediction_onboarding_content::EditPredictionOnboarding;
|
pub use edit_prediction_onboarding_content::EditPredictionOnboarding;
|
||||||
|
pub use plan_definitions::PlanDefinitions;
|
||||||
pub use young_account_banner::YoungAccountBanner;
|
pub use young_account_banner::YoungAccountBanner;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use client::{Client, UserStore, zed_urls};
|
use client::{Client, UserStore, zed_urls};
|
||||||
use gpui::{AnyElement, Entity, IntoElement, ParentElement, SharedString};
|
use gpui::{AnyElement, Entity, IntoElement, ParentElement};
|
||||||
use ui::{Divider, List, ListItem, RegisterComponent, TintColor, Tooltip, prelude::*};
|
use ui::{Divider, RegisterComponent, TintColor, Tooltip, prelude::*};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct BulletItem {
|
|
||||||
label: SharedString,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BulletItem {
|
|
||||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
|
||||||
Self {
|
|
||||||
label: label.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for BulletItem {
|
|
||||||
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
|
||||||
let line_height = 0.85 * window.line_height();
|
|
||||||
|
|
||||||
ListItem::new("list-item")
|
|
||||||
.selectable(false)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.min_w_0()
|
|
||||||
.gap_1()
|
|
||||||
.items_start()
|
|
||||||
.child(
|
|
||||||
h_flex().h(line_height).justify_center().child(
|
|
||||||
Icon::new(IconName::Dash)
|
|
||||||
.size(IconSize::XSmall)
|
|
||||||
.color(Color::Hidden),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(div().w_full().min_w_0().child(Label::new(self.label))),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum SignInStatus {
|
pub enum SignInStatus {
|
||||||
|
@ -130,107 +94,6 @@ impl ZedAiOnboarding {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn free_plan_definition(&self, cx: &mut App) -> impl IntoElement {
|
|
||||||
v_flex()
|
|
||||||
.mt_2()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Label::new("Free")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.buffer_font(cx),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new("(Current Plan)")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Custom(cx.theme().colors().text_muted.opacity(0.6)))
|
|
||||||
.buffer_font(cx),
|
|
||||||
)
|
|
||||||
.child(Divider::horizontal()),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("50 prompts per month with Claude models"))
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"2,000 accepted edit predictions with Zeta, our open-source model",
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pro_trial_definition(&self) -> impl IntoElement {
|
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("150 prompts with Claude models"))
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"Unlimited accepted edit predictions with Zeta, our open-source model",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pro_plan_definition(&self, cx: &mut App) -> impl IntoElement {
|
|
||||||
v_flex().mt_2().gap_1().map(|this| {
|
|
||||||
if self.account_too_young {
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Label::new("Pro")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Accent)
|
|
||||||
.buffer_font(cx),
|
|
||||||
)
|
|
||||||
.child(Divider::horizontal()),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("500 prompts per month with Claude models"))
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"Unlimited accepted edit predictions with Zeta, our open-source model",
|
|
||||||
))
|
|
||||||
.child(BulletItem::new("$20 USD per month")),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("pro", "Get Started")
|
|
||||||
.full_width()
|
|
||||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
|
||||||
.on_click(move |_, _window, cx| {
|
|
||||||
telemetry::event!("Upgrade To Pro Clicked", state = "young-account");
|
|
||||||
cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Label::new("Pro Trial")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Accent)
|
|
||||||
.buffer_font(cx),
|
|
||||||
)
|
|
||||||
.child(Divider::horizontal()),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
List::new()
|
|
||||||
.child(self.pro_trial_definition())
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"Try it out for 14 days for free, no credit card required",
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("pro", "Start Free Trial")
|
|
||||||
.full_width()
|
|
||||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
|
||||||
.on_click(move |_, _window, cx| {
|
|
||||||
telemetry::event!("Start Trial Clicked", state = "post-sign-in");
|
|
||||||
cx.open_url(&zed_urls::start_trial_url(cx))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_accept_terms_of_service(&self) -> AnyElement {
|
fn render_accept_terms_of_service(&self) -> AnyElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
@ -269,6 +132,7 @@ impl ZedAiOnboarding {
|
||||||
|
|
||||||
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
|
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
|
||||||
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
|
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
|
||||||
|
let plan_definitions = PlanDefinitions;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
@ -278,7 +142,7 @@ impl ZedAiOnboarding {
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.mb_2(),
|
.mb_2(),
|
||||||
)
|
)
|
||||||
.child(self.pro_trial_definition())
|
.child(plan_definitions.pro_plan(false))
|
||||||
.child(
|
.child(
|
||||||
Button::new("sign_in", "Try Zed Pro for Free")
|
Button::new("sign_in", "Try Zed Pro for Free")
|
||||||
.disabled(signing_in)
|
.disabled(signing_in)
|
||||||
|
@ -297,43 +161,132 @@ impl ZedAiOnboarding {
|
||||||
|
|
||||||
fn render_free_plan_state(&self, cx: &mut App) -> AnyElement {
|
fn render_free_plan_state(&self, cx: &mut App) -> AnyElement {
|
||||||
let young_account_banner = YoungAccountBanner;
|
let young_account_banner = YoungAccountBanner;
|
||||||
|
let plan_definitions = PlanDefinitions;
|
||||||
|
|
||||||
v_flex()
|
if self.account_too_young {
|
||||||
.relative()
|
v_flex()
|
||||||
.gap_1()
|
.relative()
|
||||||
.child(Headline::new("Welcome to Zed AI"))
|
.max_w_full()
|
||||||
.map(|this| {
|
.gap_1()
|
||||||
if self.account_too_young {
|
.child(Headline::new("Welcome to Zed AI"))
|
||||||
this.child(young_account_banner)
|
.child(young_account_banner)
|
||||||
} else {
|
.child(
|
||||||
this.child(self.free_plan_definition(cx)).when_some(
|
v_flex()
|
||||||
self.dismiss_onboarding.as_ref(),
|
.mt_2()
|
||||||
|this, dismiss_callback| {
|
.gap_1()
|
||||||
let callback = dismiss_callback.clone();
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Label::new("Pro")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Accent)
|
||||||
|
.buffer_font(cx),
|
||||||
|
)
|
||||||
|
.child(Divider::horizontal()),
|
||||||
|
)
|
||||||
|
.child(plan_definitions.pro_plan(true))
|
||||||
|
.child(
|
||||||
|
Button::new("pro", "Get Started")
|
||||||
|
.full_width()
|
||||||
|
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||||
|
.on_click(move |_, _window, cx| {
|
||||||
|
telemetry::event!(
|
||||||
|
"Upgrade To Pro Clicked",
|
||||||
|
state = "young-account"
|
||||||
|
);
|
||||||
|
cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
} else {
|
||||||
|
v_flex()
|
||||||
|
.relative()
|
||||||
|
.gap_1()
|
||||||
|
.child(Headline::new("Welcome to Zed AI"))
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.mt_2()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Label::new("Free")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.buffer_font(cx),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new("(Current Plan)")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Custom(
|
||||||
|
cx.theme().colors().text_muted.opacity(0.6),
|
||||||
|
))
|
||||||
|
.buffer_font(cx),
|
||||||
|
)
|
||||||
|
.child(Divider::horizontal()),
|
||||||
|
)
|
||||||
|
.child(plan_definitions.free_plan()),
|
||||||
|
)
|
||||||
|
.when_some(
|
||||||
|
self.dismiss_onboarding.as_ref(),
|
||||||
|
|this, dismiss_callback| {
|
||||||
|
let callback = dismiss_callback.clone();
|
||||||
|
|
||||||
this.child(
|
this.child(
|
||||||
h_flex().absolute().top_0().right_0().child(
|
h_flex().absolute().top_0().right_0().child(
|
||||||
IconButton::new("dismiss_onboarding", IconName::Close)
|
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.tooltip(Tooltip::text("Dismiss"))
|
.tooltip(Tooltip::text("Dismiss"))
|
||||||
.on_click(move |_, window, cx| {
|
.on_click(move |_, window, cx| {
|
||||||
telemetry::event!(
|
telemetry::event!(
|
||||||
"Banner Dismissed",
|
"Banner Dismissed",
|
||||||
source = "AI Onboarding",
|
source = "AI Onboarding",
|
||||||
);
|
);
|
||||||
callback(window, cx)
|
callback(window, cx)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
.child(
|
||||||
})
|
v_flex()
|
||||||
.child(self.pro_plan_definition(cx))
|
.mt_2()
|
||||||
.into_any_element()
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Label::new("Pro Trial")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Accent)
|
||||||
|
.buffer_font(cx),
|
||||||
|
)
|
||||||
|
.child(Divider::horizontal()),
|
||||||
|
)
|
||||||
|
.child(plan_definitions.pro_trial(true))
|
||||||
|
.child(
|
||||||
|
Button::new("pro", "Start Free Trial")
|
||||||
|
.full_width()
|
||||||
|
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||||
|
.on_click(move |_, _window, cx| {
|
||||||
|
telemetry::event!(
|
||||||
|
"Start Trial Clicked",
|
||||||
|
state = "post-sign-in"
|
||||||
|
);
|
||||||
|
cx.open_url(&zed_urls::start_trial_url(cx))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_trial_state(&self, _cx: &mut App) -> AnyElement {
|
fn render_trial_state(&self, _cx: &mut App) -> AnyElement {
|
||||||
|
let plan_definitions = PlanDefinitions;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.relative()
|
.relative()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
@ -343,13 +296,7 @@ impl ZedAiOnboarding {
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.mb_2(),
|
.mb_2(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(plan_definitions.pro_trial(false))
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("150 prompts with Claude models"))
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"Unlimited edit predictions with Zeta, our open-source model",
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.when_some(
|
.when_some(
|
||||||
self.dismiss_onboarding.as_ref(),
|
self.dismiss_onboarding.as_ref(),
|
||||||
|this, dismiss_callback| {
|
|this, dismiss_callback| {
|
||||||
|
@ -374,6 +321,8 @@ impl ZedAiOnboarding {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_pro_plan_state(&self, _cx: &mut App) -> AnyElement {
|
fn render_pro_plan_state(&self, _cx: &mut App) -> AnyElement {
|
||||||
|
let plan_definitions = PlanDefinitions;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(Headline::new("Welcome to Zed Pro"))
|
.child(Headline::new("Welcome to Zed Pro"))
|
||||||
|
@ -382,13 +331,7 @@ impl ZedAiOnboarding {
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.mb_2(),
|
.mb_2(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(plan_definitions.pro_plan(false))
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("500 prompts with Claude models"))
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"Unlimited edit predictions with Zeta, our open-source model",
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
Button::new("pro", "Continue with Zed Pro")
|
Button::new("pro", "Continue with Zed Pro")
|
||||||
.full_width()
|
.full_width()
|
||||||
|
@ -450,8 +393,9 @@ impl Component for ZedAiOnboarding {
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
v_flex()
|
v_flex()
|
||||||
.p_4()
|
|
||||||
.gap_4()
|
.gap_4()
|
||||||
|
.items_center()
|
||||||
|
.max_w_4_5()
|
||||||
.children(vec![
|
.children(vec![
|
||||||
single_example(
|
single_example(
|
||||||
"Not Signed-in",
|
"Not Signed-in",
|
||||||
|
@ -462,8 +406,8 @@ impl Component for ZedAiOnboarding {
|
||||||
onboarding(SignInStatus::SignedIn, false, None, false),
|
onboarding(SignInStatus::SignedIn, false, None, false),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"Account too young",
|
"Young Account",
|
||||||
onboarding(SignInStatus::SignedIn, false, None, true),
|
onboarding(SignInStatus::SignedIn, true, None, true),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"Free Plan",
|
"Free Plan",
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use client::{Client, zed_urls};
|
use client::{Client, zed_urls};
|
||||||
use cloud_llm_client::Plan;
|
use cloud_llm_client::Plan;
|
||||||
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
use gpui::{
|
||||||
use ui::{Divider, List, Vector, VectorName, prelude::*};
|
Animation, AnimationExt, AnyElement, App, IntoElement, RenderOnce, Transformation, Window,
|
||||||
|
percentage,
|
||||||
|
};
|
||||||
|
use ui::{Divider, Vector, VectorName, prelude::*};
|
||||||
|
|
||||||
use crate::{BulletItem, SignInStatus};
|
use crate::{SignInStatus, plan_definitions::PlanDefinitions};
|
||||||
|
|
||||||
#[derive(IntoElement, RegisterComponent)]
|
#[derive(IntoElement, RegisterComponent)]
|
||||||
pub struct AiUpsellCard {
|
pub struct AiUpsellCard {
|
||||||
|
@ -36,6 +39,8 @@ impl AiUpsellCard {
|
||||||
|
|
||||||
impl RenderOnce for AiUpsellCard {
|
impl RenderOnce for AiUpsellCard {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let plan_definitions = PlanDefinitions;
|
||||||
|
|
||||||
let pro_section = v_flex()
|
let pro_section = v_flex()
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
@ -51,13 +56,7 @@ impl RenderOnce for AiUpsellCard {
|
||||||
)
|
)
|
||||||
.child(Divider::horizontal()),
|
.child(Divider::horizontal()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(plan_definitions.pro_plan(false));
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("500 prompts with Claude models"))
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"Unlimited edit predictions with Zeta, our open-source model",
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let free_section = v_flex()
|
let free_section = v_flex()
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
|
@ -74,11 +73,7 @@ impl RenderOnce for AiUpsellCard {
|
||||||
)
|
)
|
||||||
.child(Divider::horizontal()),
|
.child(Divider::horizontal()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(plan_definitions.free_plan());
|
||||||
List::new()
|
|
||||||
.child(BulletItem::new("50 prompts with Claude models"))
|
|
||||||
.child(BulletItem::new("2,000 accepted edit predictions")),
|
|
||||||
);
|
|
||||||
|
|
||||||
let grid_bg = h_flex().absolute().inset_0().w_full().h(px(240.)).child(
|
let grid_bg = h_flex().absolute().inset_0().w_full().h(px(240.)).child(
|
||||||
Vector::new(VectorName::Grid, rems_from_px(500.), rems_from_px(240.))
|
Vector::new(VectorName::Grid, rems_from_px(500.), rems_from_px(240.))
|
||||||
|
@ -101,44 +96,11 @@ impl RenderOnce for AiUpsellCard {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
const DESCRIPTION: &str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
|
let description = PlanDefinitions::AI_DESCRIPTION;
|
||||||
|
|
||||||
let footer_buttons = match self.sign_in_status {
|
let card = v_flex()
|
||||||
SignInStatus::SignedIn => v_flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
Button::new("sign_in", "Start 14-day Free Pro Trial")
|
|
||||||
.full_width()
|
|
||||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
|
||||||
.on_click(move |_, _window, cx| {
|
|
||||||
telemetry::event!("Start Trial Clicked", state = "post-sign-in");
|
|
||||||
cx.open_url(&zed_urls::start_trial_url(cx))
|
|
||||||
})
|
|
||||||
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new("No credit card required")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
_ => Button::new("sign_in", "Sign In")
|
|
||||||
.full_width()
|
|
||||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
|
||||||
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
|
|
||||||
.on_click({
|
|
||||||
let callback = self.sign_in.clone();
|
|
||||||
move |_, window, cx| {
|
|
||||||
telemetry::event!("Start Trial Clicked", state = "pre-sign-in");
|
|
||||||
callback(window, cx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into_any_element(),
|
|
||||||
};
|
|
||||||
|
|
||||||
v_flex()
|
|
||||||
.relative()
|
.relative()
|
||||||
|
.flex_grow()
|
||||||
.p_4()
|
.p_4()
|
||||||
.pt_3()
|
.pt_3()
|
||||||
.border_1()
|
.border_1()
|
||||||
|
@ -146,25 +108,129 @@ impl RenderOnce for AiUpsellCard {
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(grid_bg)
|
.child(grid_bg)
|
||||||
.child(gradient_bg)
|
.child(gradient_bg);
|
||||||
.child(Label::new("Try Zed AI").size(LabelSize::Large))
|
|
||||||
|
let plans_section = h_flex()
|
||||||
|
.w_full()
|
||||||
|
.mt_1p5()
|
||||||
|
.mb_2p5()
|
||||||
|
.items_start()
|
||||||
|
.gap_6()
|
||||||
|
.child(free_section)
|
||||||
|
.child(pro_section);
|
||||||
|
|
||||||
|
let footer_container = v_flex().items_center().gap_1();
|
||||||
|
|
||||||
|
let certified_user_stamp = div()
|
||||||
|
.absolute()
|
||||||
|
.top_2()
|
||||||
|
.right_2()
|
||||||
|
.size(rems_from_px(72.))
|
||||||
.child(
|
.child(
|
||||||
div()
|
Vector::new(
|
||||||
.max_w_3_4()
|
VectorName::CertifiedUserStamp,
|
||||||
.mb_2()
|
rems_from_px(72.),
|
||||||
.child(Label::new(DESCRIPTION).color(Color::Muted)),
|
rems_from_px(72.),
|
||||||
)
|
)
|
||||||
|
.color(Color::Custom(cx.theme().colors().text_accent.alpha(0.3)))
|
||||||
|
.with_animation(
|
||||||
|
"loading_stamp",
|
||||||
|
Animation::new(Duration::from_secs(10)).repeat(),
|
||||||
|
|this, delta| this.transform(Transformation::rotate(percentage(delta))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let pro_trial_stamp = div()
|
||||||
|
.absolute()
|
||||||
|
.top_2()
|
||||||
|
.right_2()
|
||||||
|
.size(rems_from_px(72.))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
Vector::new(
|
||||||
.w_full()
|
VectorName::ProTrialStamp,
|
||||||
.mt_1p5()
|
rems_from_px(72.),
|
||||||
.mb_2p5()
|
rems_from_px(72.),
|
||||||
.items_start()
|
)
|
||||||
.gap_6()
|
.color(Color::Custom(cx.theme().colors().text.alpha(0.2))),
|
||||||
.child(free_section)
|
);
|
||||||
.child(pro_section),
|
|
||||||
)
|
match self.sign_in_status {
|
||||||
.child(footer_buttons)
|
SignInStatus::SignedIn => match self.user_plan {
|
||||||
|
None | Some(Plan::ZedFree) => card
|
||||||
|
.child(Label::new("Try Zed AI").size(LabelSize::Large))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.max_w_3_4()
|
||||||
|
.mb_2()
|
||||||
|
.child(Label::new(description).color(Color::Muted)),
|
||||||
|
)
|
||||||
|
.child(plans_section)
|
||||||
|
.child(
|
||||||
|
footer_container
|
||||||
|
.child(
|
||||||
|
Button::new("start_trial", "Start 14-day Free Pro Trial")
|
||||||
|
.full_width()
|
||||||
|
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||||
|
.when_some(self.tab_index, |this, tab_index| {
|
||||||
|
this.tab_index(tab_index)
|
||||||
|
})
|
||||||
|
.on_click(move |_, _window, cx| {
|
||||||
|
telemetry::event!(
|
||||||
|
"Start Trial Clicked",
|
||||||
|
state = "post-sign-in"
|
||||||
|
);
|
||||||
|
cx.open_url(&zed_urls::start_trial_url(cx))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new("No credit card required")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Some(Plan::ZedProTrial) => card
|
||||||
|
.child(pro_trial_stamp)
|
||||||
|
.child(Label::new("You're in the Zed Pro Trial").size(LabelSize::Large))
|
||||||
|
.child(
|
||||||
|
Label::new("Here's what you get for the next 14 days:")
|
||||||
|
.color(Color::Muted)
|
||||||
|
.mb_2(),
|
||||||
|
)
|
||||||
|
.child(plan_definitions.pro_trial(false)),
|
||||||
|
Some(Plan::ZedPro) => card
|
||||||
|
.child(certified_user_stamp)
|
||||||
|
.child(Label::new("You're in the Zed Pro plan").size(LabelSize::Large))
|
||||||
|
.child(
|
||||||
|
Label::new("Here's what you get:")
|
||||||
|
.color(Color::Muted)
|
||||||
|
.mb_2(),
|
||||||
|
)
|
||||||
|
.child(plan_definitions.pro_plan(false)),
|
||||||
|
},
|
||||||
|
// Signed Out State
|
||||||
|
_ => card
|
||||||
|
.child(Label::new("Try Zed AI").size(LabelSize::Large))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.max_w_3_4()
|
||||||
|
.mb_2()
|
||||||
|
.child(Label::new(description).color(Color::Muted)),
|
||||||
|
)
|
||||||
|
.child(plans_section)
|
||||||
|
.child(
|
||||||
|
Button::new("sign_in", "Sign In")
|
||||||
|
.full_width()
|
||||||
|
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||||
|
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
|
||||||
|
.on_click({
|
||||||
|
let callback = self.sign_in.clone();
|
||||||
|
move |_, window, cx| {
|
||||||
|
telemetry::event!("Start Trial Clicked", state = "pre-sign-in");
|
||||||
|
callback(window, cx)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +254,6 @@ impl Component for AiUpsellCard {
|
||||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||||
Some(
|
Some(
|
||||||
v_flex()
|
v_flex()
|
||||||
.p_4()
|
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.children(vec![example_group(vec![
|
.children(vec![example_group(vec![
|
||||||
single_example(
|
single_example(
|
||||||
|
@ -202,11 +267,31 @@ impl Component for AiUpsellCard {
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"Signed In State",
|
"Free Plan",
|
||||||
AiUpsellCard {
|
AiUpsellCard {
|
||||||
sign_in_status: SignInStatus::SignedIn,
|
sign_in_status: SignInStatus::SignedIn,
|
||||||
sign_in: Arc::new(|_, _| {}),
|
sign_in: Arc::new(|_, _| {}),
|
||||||
user_plan: None,
|
user_plan: Some(Plan::ZedFree),
|
||||||
|
tab_index: Some(1),
|
||||||
|
}
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Pro Trial",
|
||||||
|
AiUpsellCard {
|
||||||
|
sign_in_status: SignInStatus::SignedIn,
|
||||||
|
sign_in: Arc::new(|_, _| {}),
|
||||||
|
user_plan: Some(Plan::ZedProTrial),
|
||||||
|
tab_index: Some(1),
|
||||||
|
}
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Pro Plan",
|
||||||
|
AiUpsellCard {
|
||||||
|
sign_in_status: SignInStatus::SignedIn,
|
||||||
|
sign_in: Arc::new(|_, _| {}),
|
||||||
|
user_plan: Some(Plan::ZedPro),
|
||||||
tab_index: Some(1),
|
tab_index: Some(1),
|
||||||
}
|
}
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
|
|
39
crates/ai_onboarding/src/plan_definitions.rs
Normal file
39
crates/ai_onboarding/src/plan_definitions.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use gpui::{IntoElement, ParentElement};
|
||||||
|
use ui::{List, ListBulletItem, prelude::*};
|
||||||
|
|
||||||
|
/// Centralized definitions for Zed AI plans
|
||||||
|
pub struct PlanDefinitions;
|
||||||
|
|
||||||
|
impl PlanDefinitions {
|
||||||
|
pub const AI_DESCRIPTION: &'static str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
|
||||||
|
|
||||||
|
pub fn free_plan(&self) -> impl IntoElement {
|
||||||
|
List::new()
|
||||||
|
.child(ListBulletItem::new("50 prompts with Claude models"))
|
||||||
|
.child(ListBulletItem::new("2,000 accepted edit predictions"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pro_trial(&self, period: bool) -> impl IntoElement {
|
||||||
|
List::new()
|
||||||
|
.child(ListBulletItem::new("150 prompts with Claude models"))
|
||||||
|
.child(ListBulletItem::new(
|
||||||
|
"Unlimited edit predictions with Zeta, our open-source model",
|
||||||
|
))
|
||||||
|
.when(period, |this| {
|
||||||
|
this.child(ListBulletItem::new(
|
||||||
|
"Try it out for 14 days for free, no credit card required",
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pro_plan(&self, price: bool) -> impl IntoElement {
|
||||||
|
List::new()
|
||||||
|
.child(ListBulletItem::new("500 prompts with Claude models"))
|
||||||
|
.child(ListBulletItem::new(
|
||||||
|
"Unlimited edit predictions with Zeta, our open-source model",
|
||||||
|
))
|
||||||
|
.when(price, |this| {
|
||||||
|
this.child(ListBulletItem::new("$20 USD per month"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ impl RenderOnce for YoungAccountBanner {
|
||||||
.child(YOUNG_ACCOUNT_DISCLAIMER);
|
.child(YOUNG_ACCOUNT_DISCLAIMER);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.max_w_full()
|
||||||
.my_1()
|
.my_1()
|
||||||
.child(Banner::new().severity(ui::Severity::Warning).child(label))
|
.child(Banner::new().severity(ui::Severity::Warning).child(label))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use gpui::Transformation;
|
||||||
use gpui::{App, IntoElement, Rems, RenderOnce, Size, Styled, Window, svg};
|
use gpui::{App, IntoElement, Rems, RenderOnce, Size, Styled, Window, svg};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumIter, EnumString, IntoStaticStr};
|
use strum::{EnumIter, EnumString, IntoStaticStr};
|
||||||
|
@ -12,11 +13,13 @@ use crate::prelude::*;
|
||||||
)]
|
)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum VectorName {
|
pub enum VectorName {
|
||||||
|
AiGrid,
|
||||||
|
CertifiedUserStamp,
|
||||||
|
DebuggerGrid,
|
||||||
|
Grid,
|
||||||
|
ProTrialStamp,
|
||||||
ZedLogo,
|
ZedLogo,
|
||||||
ZedXCopilot,
|
ZedXCopilot,
|
||||||
Grid,
|
|
||||||
AiGrid,
|
|
||||||
DebuggerGrid,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VectorName {
|
impl VectorName {
|
||||||
|
@ -37,6 +40,7 @@ pub struct Vector {
|
||||||
path: Arc<str>,
|
path: Arc<str>,
|
||||||
color: Color,
|
color: Color,
|
||||||
size: Size<Rems>,
|
size: Size<Rems>,
|
||||||
|
transformation: Transformation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vector {
|
impl Vector {
|
||||||
|
@ -46,6 +50,7 @@ impl Vector {
|
||||||
path: vector.path(),
|
path: vector.path(),
|
||||||
color: Color::default(),
|
color: Color::default(),
|
||||||
size: Size { width, height },
|
size: Size { width, height },
|
||||||
|
transformation: Transformation::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +71,11 @@ impl Vector {
|
||||||
self.size = size;
|
self.size = size;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transform(mut self, transformation: Transformation) -> Self {
|
||||||
|
self.transformation = transformation;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for Vector {
|
impl RenderOnce for Vector {
|
||||||
|
@ -81,6 +91,7 @@ impl RenderOnce for Vector {
|
||||||
.h(height)
|
.h(height)
|
||||||
.path(self.path)
|
.path(self.path)
|
||||||
.text_color(self.color.color(cx))
|
.text_color(self.color.color(cx))
|
||||||
|
.with_transformation(self.transformation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
mod list;
|
mod list;
|
||||||
|
mod list_bullet_item;
|
||||||
mod list_header;
|
mod list_header;
|
||||||
mod list_item;
|
mod list_item;
|
||||||
mod list_separator;
|
mod list_separator;
|
||||||
mod list_sub_header;
|
mod list_sub_header;
|
||||||
|
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
|
pub use list_bullet_item::*;
|
||||||
pub use list_header::*;
|
pub use list_header::*;
|
||||||
pub use list_item::*;
|
pub use list_item::*;
|
||||||
pub use list_separator::*;
|
pub use list_separator::*;
|
||||||
|
|
40
crates/ui/src/components/list/list_bullet_item.rs
Normal file
40
crates/ui/src/components/list/list_bullet_item.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::{ListItem, prelude::*};
|
||||||
|
use gpui::{IntoElement, ParentElement, SharedString};
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct ListBulletItem {
|
||||||
|
label: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListBulletItem {
|
||||||
|
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for ListBulletItem {
|
||||||
|
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
|
let line_height = 0.85 * window.line_height();
|
||||||
|
|
||||||
|
ListItem::new("list-item")
|
||||||
|
.selectable(false)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.min_w_0()
|
||||||
|
.gap_1()
|
||||||
|
.items_start()
|
||||||
|
.child(
|
||||||
|
h_flex().h(line_height).justify_center().child(
|
||||||
|
Icon::new(IconName::Dash)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Hidden),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(div().w_full().min_w_0().child(Label::new(self.label))),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue