Files
shadcn-slint/ui/components/app-sidebar.slint
2026-01-30 12:56:00 +08:00

239 lines
7.5 KiB
Plaintext

import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
import { Button } from "./button.slint";
import { StatusIndicator, StatusState } from "./status-indicator.slint";
export struct NavItem {
title: string,
icon: string,
}
export component AppSidebar {
in property <[NavItem]> nav-items: [];
in-out property <int> current-page: 0;
in property <StatusState> mihomo-status: StatusState.stopped;
in property <bool> mihomo-loading: false;
callback navigate(int);
callback toggle-mihomo();
width: 240px;
Rectangle {
background: Theme.colors.background;
border-width: 1px;
border-color: Theme.colors.border;
VerticalLayout {
padding: SpacingSystem.spacing.s4;
spacing: SpacingSystem.spacing.s4;
// Header
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
Rectangle {
width: 32px;
height: 32px;
border-radius: SpacingSystem.radius.md;
background: Theme.colors.primary;
Text {
text: "C";
color: Theme.colors.primary-foreground;
font-size: Typography.sizes.lg;
font-weight: Typography.weights.bold;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash Manager";
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.semibold;
}
Text {
text: "v1.0.0";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.xs;
}
}
}
// Navigation
VerticalLayout {
spacing: SpacingSystem.spacing.s1;
Text {
text: "NAVIGATION";
color: Theme.colors.muted-foreground;
font-size: 10px;
font-weight: Typography.weights.semibold;
}
for item[index] in root.nav-items: NavButton {
icon: item.icon;
title: item.title;
active: index == root.current-page;
clicked => {
root.navigate(index);
}
}
}
Rectangle {
vertical-stretch: 1;
}
// Mihomo Status Footer
MihomoStatusBar {
status: root.mihomo-status;
loading: root.mihomo-loading;
toggle-clicked => {
root.toggle-mihomo();
}
}
}
}
}
export component NavButton {
in property <string> icon: "";
in property <string> title: "";
in property <bool> active: false;
callback clicked();
private property <bool> hovered: false;
height: 40px;
states [
active when root.active: {
container.background: Theme.colors.primary.transparentize(0.9);
icon-text.color: Theme.colors.primary;
title-text.color: Theme.colors.foreground;
}
hovered when root.hovered && !root.active: {
container.background: Theme.colors.muted.transparentize(0.5);
}
]
container := Rectangle {
background: transparent;
border-radius: SpacingSystem.radius.md;
animate background { duration: Animations.durations.fast; }
HorizontalLayout {
padding-left: SpacingSystem.spacing.s3;
padding-right: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s3;
icon-text := Text {
text: root.icon;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.lg;
vertical-alignment: center;
width: 20px;
animate color { duration: Animations.durations.fast; }
}
title-text := Text {
text: root.title;
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.medium;
vertical-alignment: center;
animate color { duration: Animations.durations.fast; }
}
}
touch := TouchArea {
clicked => {
root.clicked();
}
moved => {
root.hovered = self.has-hover;
}
}
}
}
export component MihomoStatusBar {
in property <StatusState> status: StatusState.stopped;
in property <bool> loading: false;
callback toggle-clicked();
private property <string> status-text: status == StatusState.running ? "Running" :
status == StatusState.starting ? "Starting..." :
"Stopped";
private property <color> status-color: status == StatusState.running ? #22c55e :
status == StatusState.starting ? #eab308 :
#9ca3af;
Rectangle {
background: status == StatusState.starting ? #eab30820 :
status == StatusState.running ? #22c55e20 :
transparent;
border-radius: SpacingSystem.radius.md;
animate background { duration: Animations.durations.normal; }
HorizontalLayout {
padding: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s3;
alignment: space-between;
HorizontalLayout {
spacing: SpacingSystem.spacing.s3;
StatusIndicator {
state: root.status;
}
VerticalLayout {
spacing: 2px;
Text {
text: "Clash";
color: root.status-color;
font-size: Typography.sizes.xs;
font-weight: Typography.weights.semibold;
}
Text {
text: root.status-text;
color: Theme.colors.muted-foreground;
font-size: 10px;
}
}
}
Button {
width: 32px;
height: 32px;
text: root.loading ? "..." : (status == StatusState.running ? "||" : ">");
variant: "ghost";
disabled: root.loading;
clicked => {
root.toggle-clicked();
}
}
}
}
}