175 lines
5.4 KiB
Plaintext
175 lines
5.4 KiB
Plaintext
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;
|
||
}
|
||
}
|
||
}
|
||
}
|