diff --git a/crates/storybook/src/components.rs b/crates/storybook/src/components.rs index d07c2651a02b81a63f02873484752eabdae1971e..e0ba73b866a6e6cd40006f3e0eb214a05b8b2768 100644 --- a/crates/storybook/src/components.rs +++ b/crates/storybook/src/components.rs @@ -4,11 +4,15 @@ use gpui2::{ }; use std::{marker::PhantomData, rc::Rc}; +mod avatar; mod icon_button; mod tab; +mod tool_divider; -pub(crate) use icon_button::{icon_button, ButtonVariant}; +pub(crate) use avatar::avatar; +pub(crate) use icon_button::icon_button; pub(crate) use tab::tab; +pub(crate) use tool_divider::tool_divider; struct ButtonHandlers { click: Option)>>, diff --git a/crates/storybook/src/components/avatar.rs b/crates/storybook/src/components/avatar.rs new file mode 100644 index 0000000000000000000000000000000000000000..8eff055d75c0406d73c0ac0cda512c6bcbadd0b2 --- /dev/null +++ b/crates/storybook/src/components/avatar.rs @@ -0,0 +1,39 @@ +use crate::prelude::Shape; +use crate::theme::theme; +use gpui2::elements::img; +use gpui2::style::StyleHelpers; +use gpui2::{ArcCow, IntoElement}; +use gpui2::{Element, ViewContext}; + +pub type UnknownString = ArcCow<'static, str>; + +#[derive(Element)] +pub(crate) struct Avatar { + src: ArcCow<'static, str>, + shape: Shape, +} + +pub fn avatar(src: impl Into>, shape: Shape) -> impl Element { + Avatar { + src: src.into(), + shape, + } +} + +impl Avatar { + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + let theme = theme(cx); + + let mut img = img(); + + if self.shape == Shape::Circle { + img = img.rounded_full(); + } else { + img = img.rounded_md(); + } + + img.uri(self.src.clone()) + .size_4() + .fill(theme.middle.warning.default.foreground) + } +} diff --git a/crates/storybook/src/components/icon_button.rs b/crates/storybook/src/components/icon_button.rs index 0a9b2ca285345aaca7bd91b1ff22990b0a8be407..cecb6ccfc3570d05716d79e16c6294e32b4187f9 100644 --- a/crates/storybook/src/components/icon_button.rs +++ b/crates/storybook/src/components/icon_button.rs @@ -1,3 +1,4 @@ +use crate::prelude::{ButtonVariant, UIState}; use crate::theme::theme; use gpui2::elements::svg; use gpui2::style::{StyleHelpers, Styleable}; @@ -8,22 +9,33 @@ use gpui2::{Element, ParentElement, ViewContext}; pub(crate) struct IconButton { path: &'static str, variant: ButtonVariant, + state: UIState, } -#[derive(PartialEq)] -pub enum ButtonVariant { - Ghost, - Filled, -} - -pub fn icon_button(path: &'static str, variant: ButtonVariant) -> impl Element { - IconButton { path, variant } +pub fn icon_button( + path: &'static str, + variant: ButtonVariant, + state: UIState, +) -> impl Element { + IconButton { + path, + variant, + state, + } } impl IconButton { fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { let theme = theme(cx); + let icon_color; + + if self.state == UIState::Disabled { + icon_color = theme.highest.base.disabled.foreground; + } else { + icon_color = theme.highest.base.default.foreground; + } + let mut div = div(); if self.variant == ButtonVariant::Filled { div = div.fill(theme.highest.on.default.background); @@ -39,12 +51,6 @@ impl IconButton { .fill(theme.highest.base.hovered.background) .active() .fill(theme.highest.base.pressed.background) - .child( - svg() - .path(self.path) - .w_4() - .h_4() - .fill(theme.highest.variant.default.foreground), - ) + .child(svg().path(self.path).w_4().h_4().fill(icon_color)) } } diff --git a/crates/storybook/src/components/tool_divider.rs b/crates/storybook/src/components/tool_divider.rs new file mode 100644 index 0000000000000000000000000000000000000000..c07d11dfe209ba3fe30a39485af1fb35320f913e --- /dev/null +++ b/crates/storybook/src/components/tool_divider.rs @@ -0,0 +1,19 @@ +use crate::theme::theme; +use gpui2::style::StyleHelpers; +use gpui2::{elements::div, IntoElement}; +use gpui2::{Element, ViewContext}; + +#[derive(Element)] +pub(crate) struct ToolDivider {} + +pub fn tool_divider() -> impl Element { + ToolDivider {} +} + +impl ToolDivider { + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + let theme = theme(cx); + + div().w_px().h_3().fill(theme.lowest.base.default.border) + } +} diff --git a/crates/storybook/src/modules.rs b/crates/storybook/src/modules.rs index bc8ba73b08f8fe51f218076570e6182796c75f0f..00b323fc5d911c3bc31e9d0cc8793a8f8f0999c2 100644 --- a/crates/storybook/src/modules.rs +++ b/crates/storybook/src/modules.rs @@ -1,3 +1,7 @@ +mod chat_panel; mod tab_bar; +mod title_bar; +pub(crate) use chat_panel::chat_panel; pub(crate) use tab_bar::tab_bar; +pub(crate) use title_bar::title_bar; diff --git a/crates/storybook/src/modules/chat_panel.rs b/crates/storybook/src/modules/chat_panel.rs new file mode 100644 index 0000000000000000000000000000000000000000..772bb908e0b39a16cb020fd93441e31262f4343c --- /dev/null +++ b/crates/storybook/src/modules/chat_panel.rs @@ -0,0 +1,74 @@ +use std::marker::PhantomData; + +use crate::components::icon_button; +use crate::prelude::{ButtonVariant, UIState}; +use crate::theme::theme; +use gpui2::elements::div::ScrollState; +use gpui2::style::StyleHelpers; +use gpui2::{elements::div, IntoElement}; +use gpui2::{Element, ParentElement, ViewContext}; + +#[derive(Element)] +pub struct ChatPanel { + view_type: PhantomData, + scroll_state: ScrollState, +} + +pub fn chat_panel(scroll_state: ScrollState) -> ChatPanel { + ChatPanel { + view_type: PhantomData, + scroll_state, + } +} + +impl ChatPanel { + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + let theme = theme(cx); + + div() + .h_full() + .flex() + // Header + .child( + div() + .px_2() + .flex() + .gap_2() + // Nav Buttons + .child("#gpui2"), + ) + // Chat Body + .child( + div() + .w_full() + .flex() + .flex_col() + .overflow_y_scroll(self.scroll_state.clone()) + .child("body"), + ) + // Composer + .child( + div() + .px_2() + .flex() + .gap_2() + // Nav Buttons + .child( + div() + .flex() + .items_center() + .gap_px() + .child(icon_button( + "icons/plus.svg", + ButtonVariant::Ghost, + UIState::Default, + )) + .child(icon_button( + "icons/split.svg", + ButtonVariant::Ghost, + UIState::Default, + )), + ), + ) + } +} diff --git a/crates/storybook/src/modules/tab_bar.rs b/crates/storybook/src/modules/tab_bar.rs index 06029c5dc223c0d4852c1250c3d67c2c669b2876..5a3358588ef282bbcde1dde31f5487d75271d375 100644 --- a/crates/storybook/src/modules/tab_bar.rs +++ b/crates/storybook/src/modules/tab_bar.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; -use crate::components::{icon_button, tab, ButtonVariant}; +use crate::components::{icon_button, tab}; +use crate::prelude::{ButtonVariant, UIState}; use crate::theme::theme; use gpui2::elements::div::ScrollState; use gpui2::style::StyleHelpers; @@ -40,15 +41,23 @@ impl TabBar { .flex() .items_center() .gap_px() - .child(icon_button("icons/arrow_left.svg", ButtonVariant::Filled)) - .child(icon_button("icons/arrow_right.svg", ButtonVariant::Ghost)), + .child(icon_button( + "icons/arrow_left.svg", + ButtonVariant::Ghost, + UIState::Default, + )) + .child(icon_button( + "icons/arrow_right.svg", + ButtonVariant::Ghost, + UIState::Disabled, + )), ), ) .child( div().w_0().flex_1().h_full().child( div() .flex() - .gap_px() + .gap_8() .overflow_x_scroll(self.scroll_state.clone()) .child(tab("Cargo.toml", false)) .child(tab("Channels Panel", true)) @@ -74,8 +83,16 @@ impl TabBar { .flex() .items_center() .gap_px() - .child(icon_button("icons/plus.svg", ButtonVariant::Ghost)) - .child(icon_button("icons/split.svg", ButtonVariant::Ghost)), + .child(icon_button( + "icons/plus.svg", + ButtonVariant::Ghost, + UIState::Default, + )) + .child(icon_button( + "icons/split.svg", + ButtonVariant::Ghost, + UIState::Default, + )), ), ) } diff --git a/crates/storybook/src/modules/title_bar.rs b/crates/storybook/src/modules/title_bar.rs new file mode 100644 index 0000000000000000000000000000000000000000..dfd4e4490eb7028c82d7b3aaed68cc13790ed005 --- /dev/null +++ b/crates/storybook/src/modules/title_bar.rs @@ -0,0 +1,154 @@ +use std::marker::PhantomData; + +use crate::components::{avatar, icon_button, tool_divider}; +use crate::prelude::{ButtonVariant, Shape, UIState}; +use crate::theme::theme; +use gpui2::style::{StyleHelpers, Styleable}; +use gpui2::{elements::div, IntoElement}; +use gpui2::{Element, ParentElement, ViewContext}; + +#[derive(Element)] +pub struct TitleBar { + view_type: PhantomData, +} + +pub fn title_bar() -> TitleBar { + TitleBar { + view_type: PhantomData, + } +} + +impl TitleBar { + fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { + let theme = theme(cx); + + div() + .flex() + .items_center() + .justify_between() + .w_full() + .h_8() + .fill(theme.lowest.base.default.background) + .child( + div() + .flex() + .items_center() + .h_full() + .gap_4() + .px_2() + // === Traffic Lights === // + .child( + div() + .flex() + .items_center() + .gap_2() + .child( + div() + .w_3() + .h_3() + .rounded_full() + .fill(theme.lowest.positive.default.foreground), + ) + .child( + div() + .w_3() + .h_3() + .rounded_full() + .fill(theme.lowest.warning.default.foreground), + ) + .child( + div() + .w_3() + .h_3() + .rounded_full() + .fill(theme.lowest.negative.default.foreground), + ), + ) + // === Project Info === // + .child( + div() + .flex() + .items_center() + .gap_1() + .child( + div() + .h_full() + .flex() + .items_center() + .justify_center() + .px_2() + .rounded_md() + .hover() + .fill(theme.lowest.base.hovered.background) + .active() + .fill(theme.lowest.base.pressed.background) + .child(div().text_sm().child("project")), + ) + .child( + div() + .h_full() + .flex() + .items_center() + .justify_center() + .px_2() + .rounded_md() + .text_color(theme.lowest.variant.default.foreground) + .hover() + .fill(theme.lowest.base.hovered.background) + .active() + .fill(theme.lowest.base.pressed.background) + .child(div().text_sm().child("branch")), + ), + ), + ) + .child( + div() + .flex() + .items_center() + .child( + div() + .px_2() + .flex() + .items_center() + .gap_1() + .child(icon_button( + "icons/stop_sharing.svg", + ButtonVariant::Ghost, + UIState::Default, + )) + .child(icon_button( + "icons/exit.svg", + ButtonVariant::Ghost, + UIState::Default, + )), + ) + .child(tool_divider()) + .child( + div() + .px_2() + .flex() + .items_center() + .gap_1() + .child(icon_button( + "icons/radix/mic.svg", + ButtonVariant::Ghost, + UIState::Default, + )) + .child(icon_button( + "icons/radix/speaker-loud.svg", + ButtonVariant::Ghost, + UIState::Default, + )) + .child(icon_button( + "icons/radix/desktop.svg", + ButtonVariant::Ghost, + UIState::Default, + )), + ) + .child(div().px_2().flex().items_center().child(avatar( + "https://avatars.githubusercontent.com/u/1714999?v=4", + Shape::Squircle, + ))), + ) + } +} diff --git a/crates/storybook/src/prelude.rs b/crates/storybook/src/prelude.rs new file mode 100644 index 0000000000000000000000000000000000000000..442567424ac6dc203b3287711a35ec6b1a0336a2 --- /dev/null +++ b/crates/storybook/src/prelude.rs @@ -0,0 +1,26 @@ +#[derive(PartialEq)] +pub enum ButtonVariant { + Ghost, + Filled, +} + +#[derive(PartialEq)] +pub enum Shape { + Circle, + Squircle, +} + +#[derive(PartialEq)] +pub enum UIState { + Default, + Hovered, + Active, + Focused, + Disabled, +} + +#[derive(PartialEq)] +pub enum UIToggleState { + Default, + Enabled, +} diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 1b40bc2dc482cf2bad94d8d2a662202cc8bbd2ef..fc7f861e80f9b1685b1186a7db035218ac613dfd 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -13,6 +13,7 @@ mod collab_panel; mod components; mod element_ext; mod modules; +mod prelude; mod theme; mod workspace; diff --git a/crates/storybook/src/workspace.rs b/crates/storybook/src/workspace.rs index c37b3f16ea3abac79ab659e4f3fe138e4017d20a..477255264771840efd12f61f4ef40a262cee2571 100644 --- a/crates/storybook/src/workspace.rs +++ b/crates/storybook/src/workspace.rs @@ -1,4 +1,8 @@ -use crate::{collab_panel::collab_panel, modules::tab_bar, theme::theme}; +use crate::{ + collab_panel::collab_panel, + modules::{chat_panel, tab_bar, title_bar}, + theme::theme, +}; use gpui2::{ elements::{div, div::ScrollState, img, svg}, style::{StyleHelpers, Styleable}, @@ -30,7 +34,7 @@ impl WorkspaceElement { .items_start() .text_color(theme.lowest.base.default.foreground) .fill(theme.middle.base.default.background) - .child(titlebar()) + .child(title_bar()) .child( div() .flex_1() @@ -52,7 +56,7 @@ impl WorkspaceElement { .child(tab_bar(self.tab_bar_scroll_state.clone())), ), ) - .child(collab_panel(self.right_scroll_state.clone())), + .child(chat_panel(self.right_scroll_state.clone())), ) .child(statusbar()) }