init
This commit is contained in:
47
ui/components/badge.slint
Normal file
47
ui/components/badge.slint
Normal file
@@ -0,0 +1,47 @@
|
||||
// Badge Component
|
||||
// Small status indicator or label
|
||||
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
|
||||
export component Badge {
|
||||
// Public properties
|
||||
in property <string> text: "Badge";
|
||||
in property <string> variant: "default"; // default | secondary | destructive | outline
|
||||
|
||||
// Calculate colors based on variant
|
||||
private property <color> bg-color: {
|
||||
if variant == "destructive" { Theme.colors.destructive }
|
||||
else if variant == "secondary" { Theme.colors.secondary }
|
||||
else if variant == "outline" { Colors.transparent }
|
||||
else { Theme.colors.primary }
|
||||
};
|
||||
|
||||
private property <color> text-color: {
|
||||
if variant == "destructive" { Theme.colors.destructive-foreground }
|
||||
else if variant == "secondary" { Theme.colors.secondary-foreground }
|
||||
else if variant == "outline" { Theme.colors.foreground }
|
||||
else { Theme.colors.primary-foreground }
|
||||
};
|
||||
|
||||
// Fixed height for badge
|
||||
height: 24px;
|
||||
|
||||
Rectangle {
|
||||
background: bg-color;
|
||||
border-radius: 12px; // Fixed radius for consistent rounded look
|
||||
border-width: variant == "outline" ? 1px : 0px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
// Center the text
|
||||
Text {
|
||||
text: root.text;
|
||||
font-size: Typography.sizes.xs;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: text-color;
|
||||
vertical-alignment: center;
|
||||
horizontal-alignment: center;
|
||||
x: 10px;
|
||||
width: parent.width - 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
102
ui/components/button.slint
Normal file
102
ui/components/button.slint
Normal file
@@ -0,0 +1,102 @@
|
||||
// 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();
|
||||
|
||||
// Internal state
|
||||
private property <bool> hovered: false;
|
||||
|
||||
// Calculate background color based on variant
|
||||
private property <color> 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 hover background color
|
||||
private property <color> hover-bg-color: {
|
||||
if disabled { Theme.colors.muted }
|
||||
else if variant == "destructive" { #dc2626 }
|
||||
else if variant == "outline" { Theme.colors.accent }
|
||||
else if variant == "secondary" { #e4e4e7 }
|
||||
else if variant == "ghost" { Theme.colors.accent }
|
||||
else { #27272a }
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
// Main container
|
||||
container := Rectangle {
|
||||
background: hovered ? hover-bg-color : 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Touch interaction area
|
||||
TouchArea {
|
||||
enabled: !disabled;
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
moved => {
|
||||
hovered = self.has-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
ui/components/card.slint
Normal file
93
ui/components/card.slint
Normal file
@@ -0,0 +1,93 @@
|
||||
// Card Component
|
||||
// Container for grouping related content with optional hover effect
|
||||
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Animations } from "../utils/animations.slint";
|
||||
|
||||
// Main Card component
|
||||
export component Card {
|
||||
in property <bool> hoverable: false;
|
||||
|
||||
private property <bool> hovered: false;
|
||||
|
||||
// Main container
|
||||
Rectangle {
|
||||
background: Theme.colors.card;
|
||||
border-radius: SpacingSystem.radius.lg;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
|
||||
// Hover shadow effect
|
||||
drop-shadow-blur: hovered && hoverable ? 8px : 0px;
|
||||
drop-shadow-color: #00000020;
|
||||
drop-shadow-offset-y: hovered && hoverable ? 4px : 0px;
|
||||
|
||||
animate drop-shadow-blur, drop-shadow-offset-y {
|
||||
duration: Animations.durations.normal;
|
||||
easing: Animations.ease-in-out;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s6;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
@children
|
||||
}
|
||||
|
||||
if hoverable: TouchArea {
|
||||
moved => {
|
||||
hovered = self.has-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Card Header component
|
||||
export component CardHeader {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
// Card Title component
|
||||
export component CardTitle {
|
||||
in property <string> text;
|
||||
|
||||
Text {
|
||||
text: root.text;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.card-foreground;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
// Card Description component
|
||||
export component CardDescription {
|
||||
in property <string> text;
|
||||
|
||||
Text {
|
||||
text: root.text;
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
// Card Content component
|
||||
export component CardContent {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
// Card Footer component
|
||||
export component CardFooter {
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: end;
|
||||
@children
|
||||
}
|
||||
}
|
||||
116
ui/components/dialog.slint
Normal file
116
ui/components/dialog.slint
Normal file
@@ -0,0 +1,116 @@
|
||||
// Dialog Component - Simplified version
|
||||
// Modal dialog with backdrop and smooth animations
|
||||
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Animations } from "../utils/animations.slint";
|
||||
|
||||
export component Dialog {
|
||||
// Public properties
|
||||
in-out property <bool> open: false;
|
||||
in property <string> title: "";
|
||||
in property <string> description: "";
|
||||
|
||||
// Callbacks
|
||||
callback close();
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
// Only show when open
|
||||
if open: Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #00000080;
|
||||
|
||||
// Close on backdrop click
|
||||
TouchArea {
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Dialog content container (centered)
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
// Dialog card
|
||||
Rectangle {
|
||||
width: 500px;
|
||||
background: Theme.colors.card;
|
||||
border-radius: SpacingSystem.radius.lg;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
drop-shadow-blur: 20px;
|
||||
drop-shadow-color: #00000040;
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s6;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Header
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
alignment: space-between;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Title
|
||||
Text {
|
||||
text: title;
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.card-foreground;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
|
||||
// Description
|
||||
Text {
|
||||
text: description;
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
// Close button
|
||||
Rectangle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
background: close-touch.has-hover ? Theme.colors.muted : Colors.transparent;
|
||||
|
||||
animate background {
|
||||
duration: Animations.durations.fast;
|
||||
easing: Animations.ease-in-out;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "✕";
|
||||
font-size: Typography.sizes.lg;
|
||||
color: Theme.colors.muted-foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
close-touch := TouchArea {
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent backdrop click from closing
|
||||
TouchArea {
|
||||
// Consume clicks to prevent backdrop from receiving them
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
ui/components/input.slint
Normal file
74
ui/components/input.slint
Normal file
@@ -0,0 +1,74 @@
|
||||
// Input Component
|
||||
// Text input field with focus states and optional icons
|
||||
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Animations } from "../utils/animations.slint";
|
||||
|
||||
export component Input {
|
||||
// Public properties
|
||||
in-out property <string> value: "";
|
||||
in property <string> placeholder: "";
|
||||
in property <bool> disabled: false;
|
||||
in property <bool> error: false;
|
||||
|
||||
// Callbacks
|
||||
callback edited(string);
|
||||
callback accepted(string);
|
||||
|
||||
// Internal state
|
||||
private property <bool> focused: false;
|
||||
|
||||
min-height: 40px;
|
||||
|
||||
// Main container
|
||||
Rectangle {
|
||||
background: Theme.colors.background;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
border-width: 1px;
|
||||
border-color: error ? Theme.colors.destructive :
|
||||
focused ? Theme.colors.ring :
|
||||
Theme.colors.input;
|
||||
|
||||
// Smooth border color transition
|
||||
animate border-color {
|
||||
duration: Animations.durations.fast;
|
||||
easing: Animations.ease-in-out;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Text input field
|
||||
input-field := TextInput {
|
||||
text <=> value;
|
||||
enabled: !disabled;
|
||||
font-size: Typography.sizes.base;
|
||||
color: Theme.colors.foreground;
|
||||
selection-background-color: Theme.colors.primary;
|
||||
selection-foreground-color: Theme.colors.primary-foreground;
|
||||
|
||||
edited => {
|
||||
root.edited(self.text);
|
||||
}
|
||||
|
||||
accepted => {
|
||||
root.accepted(self.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Focus detection
|
||||
FocusScope {
|
||||
key-pressed(event) => {
|
||||
focused = input-field.has-focus;
|
||||
return accept;
|
||||
}
|
||||
|
||||
key-released(event) => {
|
||||
focused = input-field.has-focus;
|
||||
return accept;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
ui/components/item.slint
Normal file
89
ui/components/item.slint
Normal file
@@ -0,0 +1,89 @@
|
||||
// Item Component
|
||||
// List item with icon, title, description and selection state
|
||||
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Animations } from "../utils/animations.slint";
|
||||
|
||||
export component Item {
|
||||
// Public properties
|
||||
in property <string> icon: ""; // Text icon/emoji
|
||||
in property <string> title;
|
||||
in property <string> description: "";
|
||||
in property <bool> selected: false;
|
||||
|
||||
// Callbacks
|
||||
callback clicked();
|
||||
|
||||
// Internal state
|
||||
private property <bool> hovered: false;
|
||||
|
||||
min-height: 60px;
|
||||
|
||||
// Main container
|
||||
Rectangle {
|
||||
background: selected ? Theme.colors.accent :
|
||||
hovered ? Theme.colors.muted :
|
||||
Colors.transparent;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
animate background {
|
||||
duration: Animations.durations.fast;
|
||||
easing: Animations.ease-in-out;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s3;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: start;
|
||||
|
||||
// Icon (if provided)
|
||||
if icon != "": Rectangle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
background: Theme.colors.muted;
|
||||
|
||||
Text {
|
||||
text: icon;
|
||||
font-size: Typography.sizes.xl;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Content area
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
alignment: center;
|
||||
|
||||
// Title
|
||||
Text {
|
||||
text: title;
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: Theme.colors.foreground;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
|
||||
// Description (if provided)
|
||||
if description != "": Text {
|
||||
text: description;
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Touch interaction
|
||||
TouchArea {
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
moved => {
|
||||
hovered = self.has-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
138
ui/components/sidebar.slint
Normal file
138
ui/components/sidebar.slint
Normal file
@@ -0,0 +1,138 @@
|
||||
// Sidebar Component
|
||||
// Collapsible navigation sidebar with menu items
|
||||
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Animations } from "../utils/animations.slint";
|
||||
|
||||
// Navigation item structure
|
||||
export struct NavItem {
|
||||
icon: string, // Text icon/emoji
|
||||
label: string,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
export component Sidebar {
|
||||
// Public properties
|
||||
in-out property <bool> collapsed: false;
|
||||
in property <[NavItem]> items: [];
|
||||
|
||||
// Callbacks
|
||||
callback item-clicked(int); // Pass item index
|
||||
callback toggle-collapsed();
|
||||
|
||||
// Calculate width based on collapsed state
|
||||
private property <length> sidebar-width: collapsed ? 60px : 240px;
|
||||
|
||||
width: sidebar-width;
|
||||
|
||||
animate width {
|
||||
duration: Animations.durations.normal;
|
||||
easing: Animations.ease-in-out;
|
||||
}
|
||||
|
||||
// Main container
|
||||
Rectangle {
|
||||
background: Theme.colors.card;
|
||||
|
||||
Rectangle {
|
||||
x: parent.width - 1px;
|
||||
width: 1px;
|
||||
height: parent.height;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
// Toggle button
|
||||
Rectangle {
|
||||
height: 40px;
|
||||
background: Colors.transparent;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Text {
|
||||
text: collapsed ? "☰" : "✕";
|
||||
font-size: Typography.sizes.xl;
|
||||
color: Theme.colors.foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
if !collapsed: Text {
|
||||
text: "Menu";
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
root.toggle-collapsed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Divider
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
// Navigation items
|
||||
for item[index] in items: Rectangle {
|
||||
height: 44px;
|
||||
background: item.active ? Theme.colors.accent : Colors.transparent;
|
||||
border-radius: SpacingSystem.radius.md;
|
||||
|
||||
states [
|
||||
hovered when touch-area.has-hover && !item.active: {
|
||||
background: Theme.colors.muted;
|
||||
}
|
||||
]
|
||||
|
||||
animate background {
|
||||
duration: Animations.durations.fast;
|
||||
easing: Animations.ease-in-out;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s2;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: start;
|
||||
|
||||
// Icon
|
||||
Text {
|
||||
text: item.icon;
|
||||
font-size: Typography.sizes.xl;
|
||||
color: item.active ? Theme.colors.accent-foreground : Theme.colors.foreground;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
// Label (only when not collapsed)
|
||||
if !collapsed: Text {
|
||||
text: item.label;
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: item.active ? Typography.weights.medium : Typography.weights.normal;
|
||||
color: item.active ? Theme.colors.accent-foreground : Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
touch-area := TouchArea {
|
||||
clicked => {
|
||||
root.item-clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
148
ui/components/toast.slint
Normal file
148
ui/components/toast.slint
Normal file
@@ -0,0 +1,148 @@
|
||||
// Toast Component
|
||||
// Notification toast with auto-dismiss and animations
|
||||
|
||||
import { Theme, Typography, SpacingSystem } from "../theme/theme.slint";
|
||||
import { Animations } from "../utils/animations.slint";
|
||||
|
||||
// Toast message structure
|
||||
export struct ToastMessage {
|
||||
message: string,
|
||||
variant: string, // default | success | error | warning
|
||||
show: bool,
|
||||
}
|
||||
|
||||
// Single toast item
|
||||
export component Toast {
|
||||
// Public properties
|
||||
in property <string> message: "";
|
||||
in property <string> variant: "default"; // default | success | error | warning
|
||||
in-out property <bool> show: false;
|
||||
|
||||
// Callbacks
|
||||
callback dismissed();
|
||||
|
||||
// Calculate colors based on variant
|
||||
private property <color> bg-color: {
|
||||
if variant == "success" { #10b981 }
|
||||
else if variant == "error" { Theme.colors.destructive }
|
||||
else if variant == "warning" { #f59e0b }
|
||||
else { Theme.colors.card }
|
||||
};
|
||||
|
||||
private property <color> text-color: {
|
||||
if variant == "success" { #ffffff }
|
||||
else if variant == "error" { Theme.colors.destructive-foreground }
|
||||
else if variant == "warning" { #ffffff }
|
||||
else { Theme.colors.card-foreground }
|
||||
};
|
||||
|
||||
private property <string> icon: {
|
||||
if variant == "success" { "✓" }
|
||||
else if variant == "error" { "✕" }
|
||||
else if variant == "warning" { "⚠" }
|
||||
else { "ℹ" }
|
||||
};
|
||||
|
||||
// Animation properties
|
||||
property <length> translate-y: show ? 0px : -20px;
|
||||
property <float> opacity-val: show ? 1.0 : 0.0;
|
||||
|
||||
animate translate-y, opacity-val {
|
||||
duration: Animations.durations.normal;
|
||||
easing: Animations.ease-out;
|
||||
}
|
||||
|
||||
width: 350px;
|
||||
height: show ? 60px : 0px;
|
||||
|
||||
if show: Rectangle {
|
||||
y: translate-y;
|
||||
opacity: opacity-val;
|
||||
background: bg-color;
|
||||
border-radius: SpacingSystem.radius.lg;
|
||||
border-width: 1px;
|
||||
border-color: Theme.colors.border;
|
||||
drop-shadow-blur: 10px;
|
||||
drop-shadow-color: #00000020;
|
||||
drop-shadow-offset-y: 4px;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: space-between;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
// Icon
|
||||
Text {
|
||||
text: icon;
|
||||
font-size: Typography.sizes.xl;
|
||||
color: text-color;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
// Message
|
||||
Text {
|
||||
text: message;
|
||||
font-size: Typography.sizes.base;
|
||||
color: text-color;
|
||||
vertical-alignment: center;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
// Close button
|
||||
Rectangle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: SpacingSystem.radius.sm;
|
||||
background: close-touch.has-hover ? #00000020 : Colors.transparent;
|
||||
|
||||
animate background {
|
||||
duration: Animations.durations.fast;
|
||||
easing: Animations.ease-in-out;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "✕";
|
||||
font-size: Typography.sizes.sm;
|
||||
color: text-color;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
close-touch := TouchArea {
|
||||
clicked => {
|
||||
root.dismissed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toast container (manages multiple toasts)
|
||||
export component ToastContainer {
|
||||
in property <[ToastMessage]> toasts: [];
|
||||
|
||||
callback toast-dismissed(int);
|
||||
|
||||
// Position at top-right corner
|
||||
width: 370px;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: start;
|
||||
|
||||
for toast[index] in toasts: Toast {
|
||||
message: toast.message;
|
||||
variant: toast.variant;
|
||||
show: toast.show;
|
||||
|
||||
dismissed => {
|
||||
root.toast-dismissed(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user