Files
shadcn-slint/ui/components/button.slint
2026-01-26 21:58:59 +08:00

139 lines
5.2 KiB
Plaintext

// Button Component
// Shadcn-style button with multiple variants and sizes
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
import { Animations } from "../utils/animations.slint";
export component Button {
// Public properties
in property <string> text: "Button";
in property <string> variant: "default"; // default | destructive | outline | secondary | ghost
in property <string> size: "md"; // sm | md | lg
in property <bool> disabled: false;
// Callbacks
callback clicked();
// Calculate text color based on variant
private property <color> text-color: {
if disabled { Theme.colors.muted-foreground }
else if variant == "destructive" { Theme.colors.destructive-foreground }
else if variant == "outline" { Theme.colors.foreground }
else if variant == "secondary" { Theme.colors.secondary-foreground }
else if variant == "ghost" { Theme.colors.foreground }
else { Theme.colors.primary-foreground }
};
// Calculate size-based dimensions
private property <length> btn-height: size == "sm" ? 36px : size == "lg" ? 44px : 40px;
private property <length> btn-padding-x: size == "sm" ? 12px : size == "lg" ? 24px : 16px;
private property <length> font-size: size == "sm" ? Typography.sizes.sm : size == "lg" ? Typography.sizes.lg : Typography.sizes.base;
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 {
border-radius: SpacingSystem.radius.md;
border-width: variant == "outline" ? 1px : 0px;
border-color: Theme.colors.border;
// 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
HorizontalLayout {
padding-left: btn-padding-x;
padding-right: btn-padding-x;
spacing: SpacingSystem.spacing.s2;
alignment: center;
// Button text
Text {
text: root.text;
color: text-color;
font-size: font-size;
font-weight: Typography.weights.medium;
vertical-alignment: center;
horizontal-alignment: center;
}
}
// 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;
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;
}
}
}
}