-
This commit is contained in:
238
ui/components/app-sidebar.slint
Normal file
238
ui/components/app-sidebar.slint
Normal file
@@ -0,0 +1,238 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user