Collapsible
An interactive element which expands/collapses.
#Import
use gpui_kit::component::collapsible::Collapsible;// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Collapsible } from "collapsible.slint";use iced::widget::{
use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Task, Theme};#Usage
#Details
Collapsible::new()
.max_w_128()
.gap_1()
.open(self.open)
.child(
"This is a collapsible component. \
Click the header to expand or collapse the content.",
)
.content(
"This is the full content of the Collapsible component. \
It is only visible when the component is expanded. \n\
You can put any content you like here, including text, images, \
or other UI elements.",
)
.child(
h_flex().justify_center().child(
Button::new("toggle1")
.icon(IconName::ChevronDown)
.label("Show more")
.when(open, |this| {
this.icon(IconName::ChevronUp).label("Show less")
})
.xsmall()
.link()
.on_click({
cx.listener(move |this, _, _, cx| {
this.open = !this.open;
cx.notify();
})
}),
),
)Collapsible {
title: "Advanced";
open: true;
Text {
text: "These options are for power users.";
color: Theme.foreground;
wrap: word-wrap;
}
}// The titled trigger form, open on boot: the title and the chevron are the
// component's own, and the caller still owns the state and the message.
let details = Collapsible::new()
.trigger(collapsible::trigger(
"Advanced",
app.advanced_open,
Message::ToggleAdvanced,
))
.open(app.advanced_open)
.content(line(
"These options are for power users.",
theme::FONT_SIZE_SM,
|theme| Tokens::of(theme).foreground,
));We can use open method to control the collapsed state. If false, the content method added child elements will be hidden.
#Animated reveal
// `animated(true)` measures the panel and moves it from nothing to its full
// height over 150ms — the duration the Slint component gives `animate
// height`. The content stays mounted while closed, so pressing the trigger
// again mid-flight reverses the reveal from the height it has already
// reached instead of restarting it.
let animated = Collapsible::new()
.trigger(collapsible::trigger(
"Advanced options",
app.animated_open,
Message::ToggleAnimated,
))
.open(app.animated_open)
.animated(true)
.content(
column![
line(
"The panel is measured from its content, then clipped to \
however far the reveal has come.",
theme::FONT_SIZE_SM,
muted,
),
line(
"Toggle it again while it moves and it turns around from \
where it is.",
theme::FONT_SIZE_SM,
muted,
),
]
.spacing(8.0),
);Opt into a reversible, measured height reveal with a stable motion ID:
Collapsible::new()
.motion_id("advanced-options")
.open(self.open)
.content(options)The content remains mounted while closed so it can be measured and immediately reverse if toggled mid-animation. Without motion_id, the component keeps the immediate mount/unmount behavior. See the GPUI Base Motion guide for timing, reduced-motion, and performance details.
#Basic
// The trigger sits beside the title and the summary stays visible: the ghost
// chevron button is the panel's trigger, and the status row never moves.
let basic_header = row![
text("Order #4189")
.size(theme::FONT_SIZE_SM)
.font(iced::Font {
weight: iced::font::Weight::Semibold,
..iced::Font::DEFAULT
})
.width(Length::Fill)
.style(|theme: &Theme| text::Style {
color: Some(Tokens::of(theme).foreground),
}),
icon_button(CHEVRONS_UP_DOWN, Message::ToggleBasic),
]
.spacing(8.0)
.align_y(Alignment::Center);
let basic = column![
basic_header,
soft_card(
row![
line("Status", theme::FONT_SIZE_SM, muted).width(Length::Fill),
Tag::new("Shipped")
.variant(TagVariant::Status)
.status(Status::Success)
.size(ControlSize::Small),
]
.spacing(8.0)
.align_y(Alignment::Center),
),
Collapsible::new().open(app.basic_open).content(
column![
card(
column![
line("Shipping address", theme::FONT_SIZE_SM, |theme| Tokens::of(
theme
)
.foreground),
line("100 Market St, San Francisco", theme::FONT_SIZE_SM, muted),
]
.spacing(4.0)
),
card(
column![
line("Items", theme::FONT_SIZE_SM, |theme| Tokens::of(theme)
.foreground),
line("2x Studio Headphones", theme::FONT_SIZE_SM, muted),
]
.spacing(4.0)
),
]
.spacing(8.0),
),
]
.spacing(8.0);A trigger beside the title, with a summary that stays visible.
Collapsible::new()
.open(self.is_open("order"))
.child(
h_flex()
.justify_between()
.child("Order #4189")
.child(
Button::new("order")
.ghost()
.xsmall()
.icon(IconName::ChevronsUpDown)
.on_click(cx.listener(|this, _, _, cx| this.toggle("order", cx))),
),
)
.child(status_row)
.content(order_details)VerticalLayout {
spacing: 8px;
HorizontalLayout {
spacing: 8px;
alignment: center;
Text {
horizontal-stretch: 1;
text: "Order #4189";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 600;
vertical-alignment: center;
}
Button {
icon: "chevrons-up-down";
variant: ButtonVariant.ghost;
size: ControlSize.xsmall;
clicked => {
root.order-open = !root.order-open;
}
}
}
Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
background: Theme.muted.with-alpha(0.3);
HorizontalLayout {
padding-left: 12px;
padding-right: 12px;
padding-top: 8px;
padding-bottom: 8px;
spacing: 8px;
alignment: center;
Text {
horizontal-stretch: 1;
text: "Status";
color: Theme.muted-foreground;
font-size: Theme.font-size-sm;
vertical-alignment: center;
}
Tag {
text: "Shipped";
variant: TagVariant.status;
status: Status.success;
size: ControlSize.small;
}
}
}
Collapsible {
open <=> root.order-open;
VerticalLayout {
spacing: 8px;
Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
VerticalLayout {
padding: 8px;
padding-left: 12px;
padding-right: 12px;
Text {
text: "Shipping address";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
}
Text {
text: "100 Market St, San Francisco";
color: Theme.muted-foreground;
font-size: Theme.font-size-sm;
wrap: word-wrap;
}
}
}
Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
VerticalLayout {
padding: 8px;
padding-left: 12px;
padding-right: 12px;
Text {
text: "Items";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
}
Text {
text: "2x Studio Headphones";
color: Theme.muted-foreground;
font-size: Theme.font-size-sm;
}
}
}
}
}
}#Row trigger
// The built-in trigger is the whole row: the component draws the title and
// the ruled chevron and reports the caller's message.
let row_trigger = Collapsible::new()
.trigger(collapsible::trigger(
"How do I reset my password?",
app.row_open,
Message::ToggleRow,
))
.open(app.row_open)
.content(line(
"Click the Forgot Password link on the sign in page, and we will send \
you an email with instructions to create a new one.",
theme::FONT_SIZE_SM,
muted,
));The whole row is the trigger.
Collapsible::new()
.open(self.is_open("faq"))
.child(
h_flex()
.id("faq")
.justify_between()
.on_click(cx.listener(|this, _, _, cx| this.toggle("faq", cx)))
.child("How do I reset my password?")
.child(chevron),
)
.content("Click the Forgot Password link on the sign in page.")Collapsible {
title: "How do I reset my password?";
Text {
text: "Click the Forgot Password link on the sign in page, and we will send you an email with instructions to create a new one.";
color: Theme.muted-foreground;
font-size: Theme.font-size-sm;
wrap: word-wrap;
}
}#Bottom trigger
// The trigger sits on the bottom edge of the card it opens, so the panel's
// content is the breakdown above it and the button below reopens it. The
// button's mark is one chevron that turns half a turn to point up as the
// panel arrives, and the panel is animated so the turn and the reveal are
// one motion. The other triggers draw their two end glyphs and swap, which
// is what the Slint example does with theirs.
let usage = Collapsible::new()
.open(app.usage_open)
.animated(true)
.content(
column![
row![
line("Requests", theme::FONT_SIZE_XS, muted).width(Length::Fill),
line("$210.84", theme::FONT_SIZE_XS, |theme| Tokens::of(theme)
.foreground),
]
.spacing(8.0),
row![
line("Active CPU", theme::FONT_SIZE_XS, muted).width(Length::Fill),
line("$21.95", theme::FONT_SIZE_XS, |theme| Tokens::of(theme)
.foreground),
]
.spacing(8.0),
row![
line("Events", theme::FONT_SIZE_XS, muted).width(Length::Fill),
line("$21.20", theme::FONT_SIZE_XS, |theme| Tokens::of(theme)
.foreground),
]
.spacing(8.0),
row![
line("Storage", theme::FONT_SIZE_XS, muted).width(Length::Fill),
line("$20.45", theme::FONT_SIZE_XS, |theme| Tokens::of(theme)
.foreground),
]
.spacing(8.0),
]
.spacing(8.0),
);
let usage_card = card(
column![
row![
line("3 days remaining in cycle", theme::FONT_SIZE_SM, |theme| {
Tokens::of(theme).foreground
})
.width(Length::Fill),
Button::new("Billing")
.outline()
.size(ControlSize::XSmall)
.on_press(Message::Notice("Billing")),
]
.spacing(8.0)
.align_y(Alignment::Center),
soft_card(
column![
row![
line("$18.08 / $20", theme::FONT_SIZE_SM, |theme| Tokens::of(
theme
)
.foreground),
line("$200", theme::FONT_SIZE_SM, |theme| Tokens::of(theme)
.foreground),
]
.spacing(8.0),
Progress::new(0.9),
]
.spacing(8.0),
),
usage,
]
.spacing(12.0),
);
let bottom = column![
usage_card,
container(
Button::new("")
.outline()
.size(ControlSize::XSmall)
.icon(turning_chevron(app.usage_open))
.on_press(Message::ToggleUsage),
)
.width(Length::Fill)
.center_x(Length::Fill),
]
.spacing(0.0);The trigger sits on the bottom edge of the card it opens.
Collapsible::new()
.open(self.is_open("usage"))
.child(usage_summary)
.content(usage_breakdown)VerticalLayout {
spacing: 0px;
Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
VerticalLayout {
padding: 12px;
spacing: 12px;
HorizontalLayout {
spacing: 8px;
alignment: center;
Text {
horizontal-stretch: 1;
text: "3 days remaining in cycle";
color: Theme.foreground;
font-size: Theme.font-size-sm;
vertical-alignment: center;
}
Button {
text: "Billing";
outline: true;
size: ControlSize.xsmall;
}
}
Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
background: Theme.muted.with-alpha(0.6);
VerticalLayout {
padding: 8px;
padding-left: 12px;
padding-right: 12px;
spacing: 8px;
HorizontalLayout {
Text {
horizontal-stretch: 1;
text: "$18.08 / $20";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
}
Text {
text: "$200";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
}
}
Progress {
value: 90;
}
}
}
Collapsible {
open <=> root.usage-open;
VerticalLayout {
spacing: 8px;
HorizontalLayout {
Text {
horizontal-stretch: 1;
text: "Requests";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
Text {
text: "$210.84";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
}
HorizontalLayout {
Text {
horizontal-stretch: 1;
text: "Active CPU";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
Text {
text: "$21.95";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
}
HorizontalLayout {
Text {
horizontal-stretch: 1;
text: "Events";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
Text {
text: "$21.20";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
}
HorizontalLayout {
Text {
horizontal-stretch: 1;
text: "Storage";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
Text {
text: "$20.45";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
}
}
}
}
}
Rectangle {
height: 0px;
horizontal-stretch: 1;
Button {
x: (parent.width - self.width) / 2;
y: -12px;
icon: root.usage-open ? "chevron-up" : "chevron-down";
outline: true;
size: ControlSize.xsmall;
clicked => {
root.usage-open = !root.usage-open;
}
}
}
}#Settings
// The trigger is the caller's own row, which is why the component reports
// only the message: here a chevron and a title open the notification panel.
let settings = column![
trigger_row(
app.settings_open,
"Notification settings",
Message::ToggleSettings
),
Collapsible::new().open(app.settings_open).content(card(
column![
Checkbox::new(app.push)
.label("Push notifications")
.on_toggle(Message::Push),
rule(),
Checkbox::new(app.email)
.label("Email notifications")
.on_toggle(Message::Email),
rule(),
Checkbox::new(app.sms)
.label("SMS notifications")
.on_toggle(Message::Sms),
]
.spacing(8.0),
)),
]
.spacing(8.0);Holds optional controls, keeping the default view short.
Collapsible::new()
.open(self.is_open("settings"))
.child(
Button::new("settings")
.outline()
.label("Notification settings")
.on_click(cx.listener(|this, _, _, cx| this.toggle("settings", cx))),
)
.content(notification_checkboxes)VerticalLayout {
spacing: 8px;
Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-md;
background: settings-touch.has-hover ? Theme.accent : transparent;
HorizontalLayout {
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
spacing: 8px;
alignment: center;
Icon {
name: root.settings-open ? "chevron-down" : "chevron-right";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "Notification settings";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
vertical-alignment: center;
}
}
settings-touch := TouchArea {
clicked => {
root.settings-open = !root.settings-open;
}
}
}
Collapsible {
open <=> root.settings-open;
Rectangle {
border-width: 1px;
border-color: Theme.border;
border-radius: Theme.radius-lg;
VerticalLayout {
padding: 8px;
padding-left: 12px;
padding-right: 12px;
spacing: 8px;
Checkbox {
label: "Push notifications";
checked <=> root.push;
}
Rectangle {
height: 1px;
background: Theme.border;
}
Checkbox {
label: "Email notifications";
checked <=> root.email;
}
Rectangle {
height: 1px;
background: Theme.border;
}
Checkbox {
label: "SMS notifications";
checked <=> root.sms;
}
}
}
}
}#Row actions
// Actions live beside the trigger and in every row: the plus adds a key, the
// ellipsis holds each row's own commands.
let keys = column![
row![
trigger_row(app.keys_open, "API Keys", Message::ToggleKeys),
icon_button(PLUS, Message::Notice("Add key")),
]
.spacing(8.0)
.align_y(Alignment::Center),
Collapsible::new().open(app.keys_open).content(
column![
key_row("Production", "PRDK230454*242SDIFPPL"),
key_row("Development", "DUILO30454*242SDIFUIP"),
key_row("Staging", "IPPODAS230454*242SDI"),
]
.spacing(8.0),
),
]
.spacing(12.0);Actions live beside the trigger, in the header and in every row.
Collapsible::new()
.open(self.is_open("api-keys"))
.child(
h_flex()
.child(api_keys_trigger)
.child(Button::new("add-key").ghost().xsmall().icon(IconName::Plus)),
)
.content(api_key_rows)VerticalLayout {
spacing: 12px;
HorizontalLayout {
spacing: 8px;
alignment: center;
Rectangle {
horizontal-stretch: 1;
background: keys-touch.has-hover ? Theme.accent : transparent;
border-radius: Theme.radius-sm;
HorizontalLayout {
padding-top: 4px;
padding-bottom: 4px;
spacing: 8px;
alignment: center;
Icon {
name: root.keys-open ? "chevron-down" : "chevron-right";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "API Keys";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
vertical-alignment: center;
}
}
keys-touch := TouchArea {
clicked => {
root.keys-open = !root.keys-open;
}
}
}
Button {
icon: "plus";
variant: ButtonVariant.ghost;
size: ControlSize.xsmall;
}
}
Collapsible {
open <=> root.keys-open;
VerticalLayout {
spacing: 8px;
HorizontalLayout {
spacing: 8px;
alignment: center;
Rectangle {
width: 20px;
height: 20px;
border-radius: Theme.radius-sm;
background: Theme.muted;
Icon {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
name: "hash";
size: 12px;
color: Theme.chart-2;
}
}
Text {
text: "Production";
color: Theme.foreground;
font-size: Theme.font-size-xs;
}
Rectangle {
horizontal-stretch: 1;
border-radius: Theme.radius-sm;
background: Theme.muted;
Text {
text: "PRDK230454*242SDIFPPL";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-family: Theme.mono-font-family;
vertical-alignment: center;
overflow: elide;
}
}
Button {
icon: "ellipsis";
variant: ButtonVariant.ghost;
size: ControlSize.xsmall;
}
}
HorizontalLayout {
spacing: 8px;
alignment: center;
Rectangle {
width: 20px;
height: 20px;
border-radius: Theme.radius-sm;
background: Theme.muted;
Icon {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
name: "hash";
size: 12px;
color: Theme.chart-2;
}
}
Text {
text: "Development";
color: Theme.foreground;
font-size: Theme.font-size-xs;
}
Rectangle {
horizontal-stretch: 1;
border-radius: Theme.radius-sm;
background: Theme.muted;
Text {
text: "DUILO30454*242SDIFUIP";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-family: Theme.mono-font-family;
vertical-alignment: center;
overflow: elide;
}
}
Button {
icon: "ellipsis";
variant: ButtonVariant.ghost;
size: ControlSize.xsmall;
}
}
HorizontalLayout {
spacing: 8px;
alignment: center;
Rectangle {
width: 20px;
height: 20px;
border-radius: Theme.radius-sm;
background: Theme.muted;
Icon {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
name: "hash";
size: 12px;
color: Theme.chart-2;
}
}
Text {
text: "Staging";
color: Theme.foreground;
font-size: Theme.font-size-xs;
}
Rectangle {
horizontal-stretch: 1;
border-radius: Theme.radius-sm;
background: Theme.muted;
Text {
text: "IPPODAS230454*242SDI";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-family: Theme.mono-font-family;
vertical-alignment: center;
overflow: elide;
}
}
Button {
icon: "ellipsis";
variant: ButtonVariant.ghost;
size: ControlSize.xsmall;
}
}
}
}
}#Nested
// Panels nest to any depth: the folder rows are triggers, and the indent is
// the caller's, so a nested panel returns to its parent's spine when it ends.
let nested = column![
folder_row(
app.components_open,
"components",
8.0,
Message::ToggleComponents
),
Collapsible::new().open(app.components_open).content(
column![
folder_row(app.ui_open, "ui", 20.0, Message::ToggleUi),
Collapsible::new().open(app.ui_open).content(
column![
file_row("button.rs", 48.0),
file_row("card.rs", 48.0),
file_row("dialog.rs", 48.0),
]
.spacing(2.0),
),
file_row("login_form.rs", 32.0),
]
.spacing(2.0),
),
file_row("main.rs", 22.0),
]
.spacing(2.0);Panels nest to any depth.
Collapsible::new()
.open(self.is_open("components-dir"))
.child(folder_row("components"))
.content(
Collapsible::new()
.open(self.is_open("ui-dir"))
.child(folder_row("ui"))
.content(v_flex().children(["button.rs", "card.rs", "dialog.rs"])),
)VerticalLayout {
Rectangle {
border-radius: Theme.radius-sm;
background: components-touch.has-hover ? Theme.accent : transparent;
HorizontalLayout {
padding: 4px;
padding-left: 8px;
padding-right: 8px;
spacing: 8px;
alignment: center;
Icon {
name: root.components-open ? "chevron-down" : "chevron-right";
size: 14px;
color: Theme.muted-foreground;
}
Icon {
name: root.components-open ? "folder-open" : "folder";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "components";
color: Theme.foreground;
font-size: Theme.font-size-sm;
vertical-alignment: center;
}
}
components-touch := TouchArea {
clicked => {
root.components-open = !root.components-open;
}
}
}
Collapsible {
open <=> root.components-open;
VerticalLayout {
Rectangle {
border-radius: Theme.radius-sm;
background: ui-touch.has-hover ? Theme.accent : transparent;
HorizontalLayout {
padding: 4px;
padding-left: 20px;
padding-right: 8px;
spacing: 8px;
alignment: center;
Icon {
name: root.ui-open ? "chevron-down" : "chevron-right";
size: 14px;
color: Theme.muted-foreground;
}
Icon {
name: root.ui-open ? "folder-open" : "folder";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "ui";
color: Theme.foreground;
font-size: Theme.font-size-sm;
vertical-alignment: center;
}
}
ui-touch := TouchArea {
clicked => {
root.ui-open = !root.ui-open;
}
}
}
Collapsible {
open <=> root.ui-open;
VerticalLayout {
spacing: 2px;
HorizontalLayout {
padding-left: 48px;
spacing: 8px;
alignment: center;
Icon {
name: "file";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "button.rs";
color: Theme.foreground;
font-size: Theme.font-size-sm;
}
}
HorizontalLayout {
padding-left: 48px;
spacing: 8px;
alignment: center;
Icon {
name: "file";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "card.rs";
color: Theme.foreground;
font-size: Theme.font-size-sm;
}
}
HorizontalLayout {
padding-left: 48px;
spacing: 8px;
alignment: center;
Icon {
name: "file";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "dialog.rs";
color: Theme.foreground;
font-size: Theme.font-size-sm;
}
}
}
}
HorizontalLayout {
padding-left: 32px;
spacing: 8px;
alignment: center;
Icon {
name: "file";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "login_form.rs";
color: Theme.foreground;
font-size: Theme.font-size-sm;
}
}
}
}
HorizontalLayout {
padding-left: 22px;
spacing: 8px;
alignment: center;
Icon {
name: "file";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "main.rs";
color: Theme.foreground;
font-size: Theme.font-size-sm;
}
}
}#Profile
// Shows who someone is, and their details only on request.
let profile = column![
pressable(
container(
row![
Avatar::new().initials("JL").size(AvatarSize::XSmall),
line("@huacnlee", theme::FONT_SIZE_SM, |theme| Tokens::of(theme)
.foreground)
.width(Length::Fill),
chevron(app.profile_open),
]
.spacing(8.0)
.align_y(Alignment::Center),
)
.padding([4.0, 8.0])
.width(Length::Fill),
Message::ToggleProfile,
),
Collapsible::new().open(app.profile_open).content(
column![
detail_line(INBOX, "Last activity", "2 hours ago"),
detail_line(CALENDAR, "Online since", "Today, 9:00 AM"),
detail_line(GLOBE, "Location", "Hong Kong"),
]
.spacing(8.0),
),
]
.spacing(12.0);Shows who someone is, and their details only on request.
Collapsible::new()
.open(self.is_open("profile"))
.child(
h_flex()
.child(Avatar::new().name("Jason Lee").xsmall())
.child("@huacnlee")
.child(chevron),
)
.content(profile_fields)VerticalLayout {
spacing: 12px;
Rectangle {
border-radius: Theme.radius-sm;
background: profile-touch.has-hover ? Theme.accent : transparent;
HorizontalLayout {
padding: 4px;
spacing: 8px;
alignment: center;
Avatar {
initials: "JL";
size: AvatarSize.xsmall;
}
Text {
horizontal-stretch: 1;
text: "@huacnlee";
color: Theme.foreground;
font-size: Theme.font-size-sm;
font-weight: 500;
vertical-alignment: center;
}
Icon {
name: root.profile-open ? "chevron-down" : "chevron-right";
size: 14px;
color: Theme.muted-foreground;
}
}
profile-touch := TouchArea {
clicked => {
root.profile-open = !root.profile-open;
}
}
}
Collapsible {
open <=> root.profile-open;
VerticalLayout {
spacing: 8px;
HorizontalLayout {
spacing: 8px;
alignment: center;
Icon {
name: "inbox";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "Last activity";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
}
Text {
text: "2 hours ago";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
}
HorizontalLayout {
spacing: 8px;
alignment: center;
Icon {
name: "calendar";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "Online since";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
}
Text {
text: "Today, 9:00 AM";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
}
HorizontalLayout {
spacing: 8px;
alignment: center;
Icon {
name: "globe";
size: 14px;
color: Theme.muted-foreground;
}
Text {
text: "Location";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
}
Text {
text: "Hong Kong";
color: Theme.foreground;
font-size: Theme.font-size-xs;
font-weight: 500;
}
}
}
}
}