feat: add spinner component
This commit is contained in:
+125
-82
@@ -25,6 +25,7 @@ use gpui_native_core::scroll_area::{ScrollArea, ScrollAxes};
|
||||
use gpui_native_core::segmented::{Segmented, SegmentedOption};
|
||||
use gpui_native_core::select::{Select, SelectOption};
|
||||
use gpui_native_core::slider::Slider;
|
||||
use gpui_native_core::spinner::{Spinner, SpinnerVariant};
|
||||
use gpui_native_core::switch::Switch;
|
||||
use gpui_native_core::toast::{Toast, ToastAction, Toasts, ToastsView};
|
||||
use gpui_native_core::toggle::Toggle;
|
||||
@@ -66,6 +67,7 @@ const CATALOG: &[(&str, &[(&str, &str)])] = &[
|
||||
("badge", "Badge"),
|
||||
("avatar", "Avatar"),
|
||||
("progress", "Progress"),
|
||||
("spinner", "Spinner"),
|
||||
("list", "List"),
|
||||
("tree", "Tree"),
|
||||
("tabs", "Tabs"),
|
||||
@@ -451,6 +453,10 @@ impl Gallery {
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
dock.register_panel(
|
||||
PanelDescriptor::new("spinner", "Spinner", |_, cx| cx.new(|_| SpinnerDemo).into()),
|
||||
cx,
|
||||
);
|
||||
dock.register_panel(
|
||||
PanelDescriptor::new("list", "List", |_, cx| {
|
||||
cx.new(|_| ListDemo { selected: None }).into()
|
||||
@@ -1305,6 +1311,47 @@ impl Render for ProgressDemo {
|
||||
}
|
||||
}
|
||||
|
||||
struct SpinnerDemo;
|
||||
|
||||
impl Render for SpinnerDemo {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = cx.theme().colors;
|
||||
|
||||
ScrollArea::new("spinner-demo")
|
||||
.size_full()
|
||||
.p_4()
|
||||
.child(demo_section(
|
||||
"spinning and determinate",
|
||||
colors,
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.child(Spinner::new("spinner-demo-spin"))
|
||||
.child(Spinner::new("spinner-demo-small").small())
|
||||
.child(Spinner::new("spinner-demo-large").large())
|
||||
.child(Spinner::new("spinner-demo-quarter").value(0.25))
|
||||
.child(Spinner::new("spinner-demo-half").value(0.5))
|
||||
.child(Spinner::new("spinner-demo-full").value(1.0)),
|
||||
))
|
||||
.child(demo_section(
|
||||
"icon variant",
|
||||
colors,
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.child(Spinner::new("spinner-demo-icon").variant(SpinnerVariant::Icon))
|
||||
.child(
|
||||
Spinner::new("spinner-demo-icon-small")
|
||||
.variant(SpinnerVariant::Icon)
|
||||
.small(),
|
||||
)
|
||||
.child(
|
||||
Spinner::new("spinner-demo-icon-held")
|
||||
.variant(SpinnerVariant::Icon)
|
||||
.value(0.5),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct ListDemo {
|
||||
selected: Option<usize>,
|
||||
}
|
||||
@@ -1526,90 +1573,86 @@ impl Render for TabsDemo {
|
||||
let colors = cx.theme().colors;
|
||||
let pill_count = self.pill_tabs.len();
|
||||
|
||||
ScrollArea::new("tabs-demo")
|
||||
.size_full()
|
||||
.p_4()
|
||||
.child(
|
||||
v_flex().gap_3()
|
||||
.child(demo_section(
|
||||
"underline",
|
||||
colors,
|
||||
Tabs::new("tabs-demo-underline")
|
||||
.selected(self.selected)
|
||||
.on_change(move |selected, _, cx| {
|
||||
view.update(cx, |demo, cx| {
|
||||
demo.selected = selected;
|
||||
ScrollArea::new("tabs-demo").size_full().p_4().child(
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.child(demo_section(
|
||||
"underline",
|
||||
colors,
|
||||
Tabs::new("tabs-demo-underline")
|
||||
.selected(self.selected)
|
||||
.on_change(move |selected, _, cx| {
|
||||
view.update(cx, |demo, cx| {
|
||||
demo.selected = selected;
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.tabs(|_, _| {
|
||||
vec![
|
||||
Tab::new("Overview"),
|
||||
Tab::new("Details"),
|
||||
Tab::new("Settings"),
|
||||
Tab::new("Locked").disabled(true),
|
||||
]
|
||||
}),
|
||||
))
|
||||
.child(match self.selected {
|
||||
0 => div()
|
||||
.child("The overview panel is the app's business — the strip only reports.")
|
||||
.into_any_element(),
|
||||
1 => div()
|
||||
.child("Details: rows, lists and trees all live under their own tabs.")
|
||||
.into_any_element(),
|
||||
_ => div()
|
||||
.child("Settings: keyboard focus rides the strip; arrows switch tabs.")
|
||||
.into_any_element(),
|
||||
})
|
||||
.child(demo_section(
|
||||
"pill",
|
||||
colors,
|
||||
Tabs::new("tabs-demo-pill")
|
||||
.variant(TabsVariant::Pill)
|
||||
.selected(self.pill_selected)
|
||||
.on_change(move |selected, _, cx| {
|
||||
view_pill.update(cx, |demo, cx| {
|
||||
demo.pill_selected = selected;
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.on_close(move |index, _, cx| {
|
||||
view_close.update(cx, |demo, cx| {
|
||||
if index < demo.pill_tabs.len() {
|
||||
demo.pill_tabs.remove(index);
|
||||
demo.pill_selected = demo
|
||||
.pill_selected
|
||||
.min(demo.pill_tabs.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.tabs(|_, _| {
|
||||
vec![
|
||||
Tab::new("Overview"),
|
||||
Tab::new("Details"),
|
||||
Tab::new("Settings"),
|
||||
Tab::new("Locked").disabled(true),
|
||||
]
|
||||
}),
|
||||
))
|
||||
.child(match self.selected {
|
||||
0 => div()
|
||||
.child(
|
||||
"The overview panel is the app's business — the strip only reports.",
|
||||
)
|
||||
.into_any_element(),
|
||||
1 => div()
|
||||
.child("Details: rows, lists and trees all live under their own tabs.")
|
||||
.into_any_element(),
|
||||
_ => div()
|
||||
.child("Settings: keyboard focus rides the strip; arrows switch tabs.")
|
||||
.into_any_element(),
|
||||
})
|
||||
.child(demo_section(
|
||||
"pill",
|
||||
colors,
|
||||
Tabs::new("tabs-demo-pill")
|
||||
.variant(TabsVariant::Pill)
|
||||
.selected(self.pill_selected)
|
||||
.on_change(move |selected, _, cx| {
|
||||
view_pill.update(cx, |demo, cx| {
|
||||
demo.pill_selected = selected;
|
||||
cx.notify();
|
||||
});
|
||||
})
|
||||
.on_close(move |index, _, cx| {
|
||||
view_close.update(cx, |demo, cx| {
|
||||
if index < demo.pill_tabs.len() {
|
||||
demo.pill_tabs.remove(index);
|
||||
demo.pill_selected = demo
|
||||
.pill_selected
|
||||
.min(demo.pill_tabs.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
})
|
||||
.tabs({
|
||||
let labels = self.pill_tabs.clone();
|
||||
move |_, _| {
|
||||
labels
|
||||
.iter()
|
||||
.map(|label| {
|
||||
Tab::new(label.clone())
|
||||
.closeable(true)
|
||||
.busy(label == "Syncing")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.muted_foreground)
|
||||
.child(format!(
|
||||
"{pill_count} tabs left; the ring on \"Syncing\" is the Spinner placeholder"
|
||||
)),
|
||||
),
|
||||
)
|
||||
});
|
||||
})
|
||||
.tabs({
|
||||
let labels = self.pill_tabs.clone();
|
||||
move |_, _| {
|
||||
labels
|
||||
.iter()
|
||||
.map(|label| {
|
||||
Tab::new(label.clone())
|
||||
.closeable(true)
|
||||
.busy(label == "Syncing")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.muted_foreground)
|
||||
.child(format!(
|
||||
"{pill_count} tabs left; \"Syncing\" spins while it loads"
|
||||
)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user