This commit is contained in:
2026-01-30 12:56:00 +08:00
parent 91f24cb462
commit dcbbda951e
89 changed files with 13486 additions and 72 deletions

174
ui/components/select.slint Normal file
View File

@@ -0,0 +1,174 @@
import { Theme, SpacingSystem, Typography, Animations } from "../theme/theme.slint";
export struct SelectOption {
label: string,
value: string,
}
export component Select {
in property <[SelectOption]> options: [];
in-out property <int> selected-index: -1;
in property <string> placeholder: "Select...";
in property <bool> enabled: true;
callback selected(int, string);
private property <bool> open: false;
private property <bool> hovered: false;
min-width: 120px;
min-height: 36px;
states [
disabled when !root.enabled: {
trigger.opacity: 0.5;
}
]
VerticalLayout {
// Trigger button
trigger := Rectangle {
height: 36px;
background: Theme.colors.background;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.input;
drop-shadow-blur: 1px;
drop-shadow-color: #00000010;
drop-shadow-offset-y: 1px;
HorizontalLayout {
padding-left: SpacingSystem.spacing.s3;
padding-right: SpacingSystem.spacing.s3;
spacing: SpacingSystem.spacing.s2;
alignment: space-between;
value-text := Text {
text: root.selected-index >= 0 && root.selected-index < root.options.length
? root.options[root.selected-index].label
: root.placeholder;
color: root.selected-index >= 0
? Theme.colors.foreground
: Theme.colors.muted-foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
chevron := Text {
text: "";
color: Theme.colors.muted-foreground;
font-size: Typography.sizes.base;
font-weight: Typography.weights.bold;
vertical-alignment: center;
width: 16px;
horizontal-alignment: center;
rotation-angle: root.open ? -90deg : 90deg;
rotation-origin-x: self.width / 2;
rotation-origin-y: self.height / 2;
animate rotation-angle { duration: Animations.durations.fast; easing: Animations.ease-out; }
}
}
touch := TouchArea {
enabled: root.enabled;
clicked => {
root.open = !root.open;
}
moved => {
root.hovered = self.has-hover;
}
}
}
// Dropdown menu
if root.open: dropdown := Rectangle {
y: trigger.height + 4px;
background: Theme.colors.background;
border-radius: SpacingSystem.radius.md;
border-width: 1px;
border-color: Theme.colors.border;
drop-shadow-blur: 8px;
drop-shadow-color: #00000020;
drop-shadow-offset-y: 4px;
VerticalLayout {
padding: SpacingSystem.spacing.s1;
for option[index] in root.options: SelectItem {
label: option.label;
selected: index == root.selected-index;
clicked => {
root.selected-index = index;
root.selected(index, option.value);
root.open = false;
}
}
}
}
}
}
component SelectItem {
in property <string> label: "";
in property <bool> selected: false;
callback clicked();
private property <bool> hovered: false;
height: 32px;
states [
selected when root.selected: {
container.background: Theme.colors.accent;
}
hovered when root.hovered && !root.selected: {
container.background: Theme.colors.accent.transparentize(0.5);
}
]
container := Rectanglen background: transparent;
border-radius: SpacingSystem.radius.sm;
animate background { duration: Animations.durations.fast; }
HorizontalLayout {
padding-left: SpacingSystem.spacing.s2;
padding-right: SpacingSystem.spacing.s2;
spacing: SpacingSystem.spacing.s2;
if root.selected: checkmark := Text {
text: "✓";
color: Theme.colors.primary;
font-size: Typography.sizes.sm;
font-weight: Typography.weights.bold;
vertical-alignment: center;
width: 16px;
}
Text {
text: root.label;
color: Theme.colors.foreground;
font-size: Typography.sizes.sm;
vertical-alignment: center;
}
}
touch := TouchArea {
clicked => {
root.clicked();
}
moved => {
root.hovered = self.has-hover;
}
}
}
}