diff --git a/ui/components/button.slint b/ui/components/button.slint index dad2c95..6d919b2 100644 --- a/ui/components/button.slint +++ b/ui/components/button.slint @@ -14,16 +14,6 @@ export component Button { // Callbacks callback clicked(); - // Calculate background color based on variant - private property base-bg-color: { - if disabled { Theme.colors.muted } - else if variant == "destructive" { Theme.colors.destructive } - else if variant == "outline" { Colors.transparent } - else if variant == "secondary" { Theme.colors.secondary } - else if variant == "ghost" { Colors.transparent } - else { Theme.colors.primary } - }; - // Calculate text color based on variant private property text-color: { if disabled { Theme.colors.muted-foreground } @@ -42,17 +32,74 @@ export component Button { min-width: btn-height; min-height: btn-height; + // Focus scope for keyboard navigation + focus-scope := FocusScope { + enabled: !disabled; + + key-pressed(event) => { + if (event.text == " " || event.text == "\n") { + root.clicked(); + return accept; + } + return reject; + } + } + // Main container container := Rectangle { - background: base-bg-color; border-radius: SpacingSystem.radius.md; border-width: variant == "outline" ? 1px : 0px; border-color: Theme.colors.border; - // Smooth transitions - animate background { - duration: Animations.durations.fast; - easing: Animations.ease-in-out; + // Touch interaction area (must be defined before using touch.pressed/has-hover) + touch := TouchArea { + enabled: !disabled; + + clicked => { + // Don't set focus on mouse click (shadcn style) + root.clicked(); + } + } + + // Background with state-based colors (shadcn style) + background-rect := Rectangle { + width: 100%; + height: 100%; + border-radius: parent.border-radius; + + // Calculate background color based on variant and state + background: { + if root.disabled { + Theme.colors.muted + } else if root.variant == "destructive" { + if touch.pressed { Theme.colors.destructive.darker(0.1) } + else if touch.has-hover { Theme.colors.destructive.darker(0.05) } + else { Theme.colors.destructive } + } else if root.variant == "outline" { + if touch.pressed { Theme.colors.accent.darker(0.05) } + else if touch.has-hover { Theme.colors.accent } + else { Colors.transparent } + } else if root.variant == "secondary" { + if touch.pressed { Theme.colors.secondary.darker(0.1) } + else if touch.has-hover { Theme.colors.secondary.darker(0.05) } + else { Theme.colors.secondary } + } else if root.variant == "ghost" { + if touch.pressed { Theme.colors.accent.darker(0.05) } + else if touch.has-hover { Theme.colors.accent } + else { Colors.transparent } + } else { + // default variant + if touch.pressed { Theme.colors.primary.darker(0.1) } + else if touch.has-hover { Theme.colors.primary.darker(0.05) } + else { Theme.colors.primary } + } + }; + + // Smooth transitions + animate background { + duration: Animations.durations.fast; + easing: Animations.ease-in-out; + } } // Content layout @@ -72,38 +119,19 @@ export component Button { horizontal-alignment: center; } } - - // Touch interaction area - touch := TouchArea { - enabled: !disabled; - - clicked => { - root.clicked(); - } - } - - // Combined overlay for both hover and press effects - overlay := Rectangle { + + // Focus ring only for keyboard navigation (shadcn style) + // Will only show when using Tab key, not on mouse click + focus-ring := Rectangle { width: 100%; height: 100%; border-radius: SpacingSystem.radius.md; - - // 直接根据状态设置背景色,不使用 states 块 - background: { - if disabled { - Colors.transparent - } else if touch.pressed { - #00000030 // 按压时深色 - } else if touch.has-hover { - #ffffff10 // 悬停时浅色 - } else { - Colors.transparent - } - }; - - animate background { - duration: 200ms; - easing: ease-in-out; + border-width: focus-scope.has-focus ? 2px : 0px; + border-color: Theme.colors.ring; + + animate border-width { + duration: Animations.durations.fast; + easing: Animations.ease-in-out; } } }