Add to masked Label and support children to Button (#50)
https://github.com/user-attachments/assets/9583ce15-b801-45f9-aafb-54f0ecdf7dc9 - Add masked mode to Label. - Add to support any children to Button. - Fix Popover to support selected state style.
This commit is contained in:
parent
a69d30be6a
commit
e54d1f600b
8 changed files with 95 additions and 13 deletions
6
assets/icons/eye-off.svg
Normal file
6
assets/icons/eye-off.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye-off">
|
||||
<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/>
|
||||
<path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/>
|
||||
<path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/>
|
||||
<path d="m2 2 20 20"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 510 B |
4
assets/icons/eye.svg
Normal file
4
assets/icons/eye.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye">
|
||||
<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 364 B |
|
|
@ -84,6 +84,19 @@ impl Render for ButtonStory {
|
|||
.icon(Icon::new(IconName::Maximize))
|
||||
.style(ButtonStyle::Secondary)
|
||||
.on_click(Self::on_click),
|
||||
)
|
||||
.child(
|
||||
Button::new("button-icon-4", cx)
|
||||
.style(ButtonStyle::Secondary)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child("Custom Child")
|
||||
.child(IconName::ChevronDown)
|
||||
.child(IconName::Eye),
|
||||
)
|
||||
.on_click(Self::on_click),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,14 @@ use gpui::{
|
|||
WindowContext,
|
||||
};
|
||||
|
||||
use ui::{checkbox::Checkbox, h_flex, radio::Radio, v_flex, Disableable as _, Selection};
|
||||
use ui::{
|
||||
button::{Button, ButtonStyle},
|
||||
checkbox::Checkbox,
|
||||
h_flex,
|
||||
label::Label,
|
||||
radio::Radio,
|
||||
v_flex, Clickable, Disableable as _, IconName, Selection,
|
||||
};
|
||||
|
||||
pub struct CheckboxStory {
|
||||
check1: Selection,
|
||||
|
|
@ -11,6 +18,7 @@ pub struct CheckboxStory {
|
|||
check3: Selection,
|
||||
select1: bool,
|
||||
select2: bool,
|
||||
masked: bool,
|
||||
}
|
||||
|
||||
impl CheckboxStory {
|
||||
|
|
@ -21,6 +29,7 @@ impl CheckboxStory {
|
|||
check3: Selection::Selected,
|
||||
select1: false,
|
||||
select2: true,
|
||||
masked: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +48,25 @@ impl Render for CheckboxStory {
|
|||
v_flex()
|
||||
.p_4()
|
||||
.gap_6()
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(Label::new("9,182,1 USD").text_2xl().masked(self.masked))
|
||||
.child(
|
||||
Button::new("btn-mask", cx)
|
||||
.style(ButtonStyle::Ghost)
|
||||
.icon(if self.masked {
|
||||
IconName::EyeOff
|
||||
} else {
|
||||
IconName::Eye
|
||||
})
|
||||
.on_click(cx.listener(|this, _, _| {
|
||||
this.masked = !this.masked;
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(Label::new("500 USD").text_xl().masked(self.masked))
|
||||
.child(
|
||||
v_flex().items_start().justify_start().gap_6().child(
|
||||
h_flex()
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ use crate::{
|
|||
Clickable, Disableable, Icon, Selectable, Size, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, px, ClickEvent, DefiniteLength, Div, ElementId, FocusHandle,
|
||||
Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce,
|
||||
SharedString, StatefulInteractiveElement as _, Styled, WindowContext,
|
||||
div, prelude::FluentBuilder as _, px, AnyElement, ClickEvent, DefiniteLength, Div, ElementId,
|
||||
FocusHandle, Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled, WindowContext,
|
||||
};
|
||||
|
||||
pub enum ButtonRounded {
|
||||
|
|
@ -40,6 +40,7 @@ pub struct Button {
|
|||
focus_handle: FocusHandle,
|
||||
icon: Option<Icon>,
|
||||
label: Option<SharedString>,
|
||||
children: Vec<AnyElement>,
|
||||
disabled: bool,
|
||||
selected: bool,
|
||||
width: Option<DefiniteLength>,
|
||||
|
|
@ -70,6 +71,7 @@ impl Button {
|
|||
tooltip: None,
|
||||
on_click: None,
|
||||
loading: false,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +173,12 @@ impl Styled for Button {
|
|||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Button {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Button {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let style: ButtonStyle = self.style;
|
||||
|
|
@ -186,7 +194,7 @@ impl RenderOnce for Button {
|
|||
.when_some(self.width, |this, width| this.w(width))
|
||||
.when_some(self.height, |this, height| this.h(height))
|
||||
.map(|this| {
|
||||
if self.label.is_none() {
|
||||
if self.label.is_none() && self.children.is_empty() {
|
||||
// Icon Button
|
||||
match self.size {
|
||||
Size::Size(px) => this.size(px),
|
||||
|
|
@ -268,6 +276,7 @@ impl RenderOnce for Button {
|
|||
this.child(Indicator::new().size(self.size).color(text_color))
|
||||
})
|
||||
.when_some(self.label, |this, label| this.child(label))
|
||||
.children(self.children)
|
||||
.map(|this| match self.size {
|
||||
Size::XSmall => this.text_xs(),
|
||||
Size::Small => this.text_sm(),
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ pub enum IconName {
|
|||
ArrowRight,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
EyeOff,
|
||||
}
|
||||
|
||||
impl IconName {
|
||||
|
|
@ -53,6 +55,8 @@ impl IconName {
|
|||
IconName::ArrowRight => "icons/arrow-right.svg",
|
||||
IconName::ChevronLeft => "icons/chevron-left.svg",
|
||||
IconName::ChevronRight => "icons/chevron-right.svg",
|
||||
IconName::Eye => "icons/eye.svg",
|
||||
IconName::EyeOff => "icons/eye-off.svg",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ pub struct Label {
|
|||
label: SharedString,
|
||||
multiple_lines: bool,
|
||||
align: TextAlign,
|
||||
marked: bool,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
|
|
@ -28,6 +29,7 @@ impl Label {
|
|||
label: label.into(),
|
||||
multiple_lines: true,
|
||||
align: TextAlign::default(),
|
||||
marked: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -40,6 +42,11 @@ impl Label {
|
|||
self.align = align;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn masked(mut self, masked: bool) -> Self {
|
||||
self.marked = masked;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Label {
|
||||
|
|
@ -56,6 +63,12 @@ impl RenderOnce for Label {
|
|||
self.label
|
||||
};
|
||||
|
||||
let text_display = if self.marked {
|
||||
"*".repeat(text.chars().count())
|
||||
} else {
|
||||
text.to_string()
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.map(|this| match self.align {
|
||||
TextAlign::Left => this.justify_start(),
|
||||
|
|
@ -63,6 +76,6 @@ impl RenderOnce for Label {
|
|||
TextAlign::Right => this.justify_end(),
|
||||
})
|
||||
.text_color(cx.theme().foreground)
|
||||
.child(self.base.child(text))
|
||||
.child(self.base.child(text_display))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ enum PopupMode {
|
|||
pub struct Popover<M: ManagedView> {
|
||||
id: ElementId,
|
||||
anchor: AnchorCorner,
|
||||
trigger: Option<Box<dyn FnOnce(&WindowContext) -> AnyElement + 'static>>,
|
||||
trigger: Option<Box<dyn FnOnce(bool, &WindowContext) -> AnyElement + 'static>>,
|
||||
content: Option<Rc<dyn Fn(&mut WindowContext) -> View<M> + 'static>>,
|
||||
mouse_button: MouseButton,
|
||||
mode: PopupMode,
|
||||
|
|
@ -103,7 +103,9 @@ where
|
|||
where
|
||||
T: Selectable + IntoElement + 'static,
|
||||
{
|
||||
self.trigger = Some(Box::new(|_| trigger.into_any_element()));
|
||||
self.trigger = Some(Box::new(|is_open, _| {
|
||||
trigger.selected(is_open).into_any_element()
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +120,7 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
fn render_trigger(&mut self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render_trigger(&mut self, is_open: bool, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let base = div().id("popover-trigger");
|
||||
|
||||
if self.trigger.is_none() {
|
||||
|
|
@ -127,7 +129,7 @@ where
|
|||
|
||||
let trigger = self.trigger.take().unwrap();
|
||||
|
||||
base.child((trigger)(cx)).into_element()
|
||||
base.child((trigger)(is_open, cx)).into_element()
|
||||
}
|
||||
|
||||
fn resolved_corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
|
||||
|
|
@ -214,13 +216,13 @@ impl<M: ManagedView> Element for Popover<M> {
|
|||
let mode = self.mode;
|
||||
|
||||
self.with_element_state(id.unwrap(), cx, |view, element_state, cx| {
|
||||
let mut trigger_element = view.render_trigger(cx).into_any_element();
|
||||
let trigger_layout_id = trigger_element.request_layout(cx);
|
||||
|
||||
let mut popover_layout_id = None;
|
||||
let mut popover_element = None;
|
||||
let mut is_open = false;
|
||||
|
||||
if let Some(content_view) = element_state.content_view.borrow_mut().as_mut() {
|
||||
is_open = true;
|
||||
|
||||
let mut anchored = anchored().snap_to_window().anchor(view.anchor);
|
||||
if let Some(trigger_bounds) = element_state.trigger_bounds {
|
||||
anchored = anchored.position(view.resolved_corner(trigger_bounds));
|
||||
|
|
@ -270,6 +272,9 @@ impl<M: ManagedView> Element for Popover<M> {
|
|||
popover_element = Some(element);
|
||||
}
|
||||
|
||||
let mut trigger_element = view.render_trigger(is_open, cx).into_any_element();
|
||||
let trigger_layout_id = trigger_element.request_layout(cx);
|
||||
|
||||
let layout_id = cx.request_layout(
|
||||
Style::default(),
|
||||
popover_layout_id.into_iter().chain(Some(trigger_layout_id)),
|
||||
|
|
|
|||
Loading…
Reference in a new issue