Files
shadcn-slint/ui/components/select.slint
2026-01-30 12:56:00 +08:00

175 lines
5.4 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}
}