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 current-page: 0; in property mihomo-status: StatusState.stopped; in property 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 icon: ""; in property title: ""; in property active: false; callback clicked(); private property 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 status: StatusState.stopped; in property loading: false; callback toggle-clicked(); private property status-text: status == StatusState.running ? "Running" : status == StatusState.starting ? "Starting..." : "Stopped"; private property 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(); } } } } }