Match split-pane header to design (accent dot + tab title)

The design's split.jsx Pane header leads with an 8 px brand-accent dot,
the lock indicator, host name in INK, then the tab title in INK_4.
The previous header rendered a 24 px brand glyph plus the URL path —
visually heavier and less informative once both panes share most of
their URL prefix.

* brand_glyph: `brand_accent_color(brand)` and `accent_color_for_host`
  collapse each Brand to one design-matching dot color (Figma 7c6cf7,
  Linear 5e6ad2, etc.).
* split_pane: 32 → 30 px header, glyph → 8 px dot, URL path → tab
  title in INK_4. `pane_path_label` deleted; nothing else used it.

cargo test --workspace: 440 passed, 0 failed.
This commit is contained in:
2026-05-09 20:34:06 -04:00
parent a92f478e21
commit cbcc45bd7b
4 changed files with 51 additions and 28 deletions
@@ -102,6 +102,35 @@ pub(crate) fn render_glyph_for(
render_fallback(fallback_initial, size) render_fallback(fallback_initial, size)
} }
/// Single-color accent for a brand — used by surfaces (split-pane headers,
/// tab indicators) that need a readable dot rather than the full brand mark.
/// Matches the design's `accent` prop on each pane.
pub(crate) fn brand_accent_color(brand: Brand) -> u32 {
match brand {
Brand::Notion => 0x111111,
Brand::YouTube => 0xff3b2d,
Brand::Dribbble => 0xea4c89,
Brand::X => 0x1d1c1a,
Brand::Vercel => 0x1d1c1a,
Brand::Behance => 0x1769ff,
Brand::Figma => 0x7c6cf7,
Brand::Slack => 0xe01e5a,
Brand::GitHub => 0x1d1c1a,
Brand::Linear => 0x5e6ad2,
Brand::Reading => 0xc96442,
Brand::News => 0x3a3733,
}
}
/// Best-effort accent for any host. Falls back to the warm system accent so
/// unknown sites still render the design's colored dot instead of looking
/// stranded.
pub(crate) fn accent_color_for_host(host: Option<&str>) -> u32 {
host.and_then(Brand::from_host)
.map(brand_accent_color)
.unwrap_or(colors::ACCENT)
}
fn render_notion(size: f32) -> AnyElement { fn render_notion(size: f32) -> AnyElement {
flat_square(size, 0xffffff, fontable(size, 0.65, 700.0, 0x111111, "N")) flat_square(size, 0xffffff, fontable(size, 0.65, 700.0, 0x111111, "N"))
} }
+2 -2
View File
@@ -19,7 +19,7 @@ pub(crate) mod typography;
pub(crate) mod wallpaper; pub(crate) mod wallpaper;
pub(crate) use appearance_form::render_appearance_form; pub(crate) use appearance_form::render_appearance_form;
pub(crate) use brand_glyph::render_glyph_for; pub(crate) use brand_glyph::{accent_color_for_host, render_glyph_for};
pub(crate) use command_overlay::render_command_overlay; pub(crate) use command_overlay::render_command_overlay;
pub(crate) use home::render_home_page; pub(crate) use home::render_home_page;
pub(crate) use plugin_detail_view::render_plugin_detail_view; pub(crate) use plugin_detail_view::render_plugin_detail_view;
@@ -27,7 +27,7 @@ pub(crate) use settings_layout::render_settings_landing;
pub(crate) use sidebar::{panel_bg, panel_shadow}; pub(crate) use sidebar::{panel_bg, panel_shadow};
pub(crate) use sidebar_header::render_sidebar_header; pub(crate) use sidebar_header::render_sidebar_header;
pub(crate) use split_pane::{ pub(crate) use split_pane::{
pane_host_label, pane_path_label, pane_url_is_secure, render_compact_split_canvas, pane_host_label, pane_url_is_secure, render_compact_split_canvas,
render_split_pane_header, render_split_pane_header,
}; };
pub(crate) use topbar::render_topbar; pub(crate) use topbar::render_topbar;
+17 -17
View File
@@ -7,20 +7,25 @@ use gpui::{
use gpui_component::IconName; use gpui_component::IconName;
use crate::shell::ElyShell; use crate::shell::ElyShell;
use crate::shell::chrome::render_glyph_for; use crate::shell::chrome::accent_color_for_host;
/// Pane header chrome for a split tab.
///
/// Matches the design's split.jsx Pane header: 30 px tall, warm card bg,
/// hairline divider, an 8 px brand-accent dot, the secure indicator, host
/// name in INK, the tab title in INK_4, then reload + close glyphs.
pub(crate) fn render_split_pane_header( pub(crate) fn render_split_pane_header(
host: String, host: String,
path: String, title: String,
secure: bool, secure: bool,
glyph_initial: String,
close_tab_id: TabId, close_tab_id: TabId,
cx: &mut Context<ElyShell>, cx: &mut Context<ElyShell>,
) -> AnyElement { ) -> AnyElement {
let lock_or_globe = if secure { IconName::Search } else { IconName::Globe }; let lock_or_globe = if secure { IconName::Search } else { IconName::Globe };
let accent = accent_color_for_host(Some(host.as_str()));
div() div()
.h(px(32.0)) .h(px(30.0))
.px(px(10.0)) .px(px(10.0))
.gap(px(8.0)) .gap(px(8.0))
.flex() .flex()
@@ -29,7 +34,12 @@ pub(crate) fn render_split_pane_header(
.border_b_1() .border_b_1()
.border_color(rgb(colors::HAIRLINE)) .border_color(rgb(colors::HAIRLINE))
.bg(rgb(0xf6f4ef)) .bg(rgb(0xf6f4ef))
.child(render_glyph_for(Some(host.as_str()), &glyph_initial, 16.0)) .child(
div()
.size(px(8.0))
.rounded_full()
.bg(rgb(accent)),
)
.child( .child(
div() div()
.text_color(rgb(colors::INK_3)) .text_color(rgb(colors::INK_3))
@@ -38,7 +48,7 @@ pub(crate) fn render_split_pane_header(
) )
.child( .child(
div() div()
.text_size(px(11.5)) .text_size(px(11.0))
.font_weight(FontWeight(500.0)) .font_weight(FontWeight(500.0))
.text_color(rgb(colors::INK)) .text_color(rgb(colors::INK))
.child(host), .child(host),
@@ -50,7 +60,7 @@ pub(crate) fn render_split_pane_header(
.truncate() .truncate()
.text_size(px(11.0)) .text_size(px(11.0))
.text_color(rgb(colors::INK_4)) .text_color(rgb(colors::INK_4))
.child(path), .child(title),
) )
.child(render_reload_glyph(close_tab_id.clone(), cx)) .child(render_reload_glyph(close_tab_id.clone(), cx))
.child(render_close_glyph(close_tab_id, cx)) .child(render_close_glyph(close_tab_id, cx))
@@ -95,16 +105,6 @@ pub(crate) fn pane_host_label(tab: &BrowserTab) -> String {
.unwrap_or_else(|| tab.title().to_string()) .unwrap_or_else(|| tab.title().to_string())
} }
pub(crate) fn pane_path_label(tab: &BrowserTab) -> String {
let url = tab.url().as_str();
let path_start = url.find("://").map(|prefix| prefix + 3).unwrap_or(0);
let from_path = &url[path_start..];
match from_path.find('/') {
Some(slash) => from_path[slash..].to_string(),
None => String::new(),
}
}
pub(crate) fn pane_url_is_secure(tab: &BrowserTab) -> bool { pub(crate) fn pane_url_is_secure(tab: &BrowserTab) -> bool {
let url = tab.url().as_str(); let url = tab.url().as_str();
url.starts_with("https://") || url.starts_with("ely://") url.starts_with("https://") || url.starts_with("ely://")
+3 -9
View File
@@ -11,7 +11,7 @@ use gpui_component::{
}; };
use super::chrome::{ use super::chrome::{
pane_host_label, pane_path_label, pane_url_is_secure, render_compact_split_canvas, pane_host_label, pane_url_is_secure, render_compact_split_canvas,
render_split_pane_header, render_split_pane_header,
}; };
use super::{ElyShell, ShellState}; use super::{ElyShell, ShellState};
@@ -190,14 +190,8 @@ impl ElyShell {
let close_tab_id = tab.id().clone(); let close_tab_id = tab.id().clone();
let border = if active { colors::ACCENT } else { colors::HAIRLINE_STRONG }; let border = if active { colors::ACCENT } else { colors::HAIRLINE_STRONG };
let host = pane_host_label(tab); let host = pane_host_label(tab);
let path = pane_path_label(tab); let title = tab.title().to_string();
let secure = pane_url_is_secure(tab); let secure = pane_url_is_secure(tab);
let glyph_initial = tab
.title()
.chars()
.next()
.unwrap_or('?')
.to_string();
div() div()
.id(SharedString::from(format!("split-pane-{}", tab.id().as_str()))) .id(SharedString::from(format!("split-pane-{}", tab.id().as_str())))
@@ -217,7 +211,7 @@ impl ElyShell {
.on_click(cx.listener(move |shell, _, window, cx| { .on_click(cx.listener(move |shell, _, window, cx| {
shell.select_tab(&tab_id, window, cx); shell.select_tab(&tab_id, window, cx);
})) }))
.child(render_split_pane_header(host, path, secure, glyph_initial, close_tab_id, cx)) .child(render_split_pane_header(host, title, secure, close_tab_id, cx))
.child(div().flex_1().min_h_0().overflow_hidden().child(if compact_canvas { .child(div().flex_1().min_h_0().overflow_hidden().child(if compact_canvas {
render_compact_split_canvas(tab) render_compact_split_canvas(tab)
} else { } else {