init
This commit is contained in:
26
ui/appwindow.slint
Normal file
26
ui/appwindow.slint
Normal file
@@ -0,0 +1,26 @@
|
||||
import { VerticalBox, Button } from "std-widgets.slint";
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
in-out property <string> greeting: "Hello, World!";
|
||||
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
title: "Slint Hello World";
|
||||
|
||||
VerticalBox {
|
||||
alignment: center;
|
||||
|
||||
Text {
|
||||
text: greeting;
|
||||
font-size: 24px;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Click Me!";
|
||||
clicked => {
|
||||
greeting = "Hello from Slint!";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
553
ui/demo.slint
Normal file
553
ui/demo.slint
Normal file
@@ -0,0 +1,553 @@
|
||||
// Demo Application - Task Manager
|
||||
// Showcases all shadcn-style UI components
|
||||
|
||||
import { Theme, Typography, SpacingSystem } from "theme/theme.slint";
|
||||
import { Button } from "components/button.slint";
|
||||
import { Input } from "components/input.slint";
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "components/card.slint";
|
||||
import { Badge } from "components/badge.slint";
|
||||
import { Item } from "components/item.slint";
|
||||
import { Sidebar, NavItem } from "components/sidebar.slint";
|
||||
import { Dialog } from "components/dialog.slint";
|
||||
import { ToastContainer, ToastMessage } from "components/toast.slint";
|
||||
|
||||
export component Demo inherits Window {
|
||||
title: "Slint Shadcn UI - Task Manager Demo";
|
||||
background: Theme.colors.background;
|
||||
|
||||
// Window size settings - now resizable
|
||||
min-width: 800px;
|
||||
min-height: 600px;
|
||||
preferred-width: 1400px;
|
||||
preferred-height: 900px;
|
||||
|
||||
// Application state
|
||||
in-out property <string> new-task-title: "";
|
||||
in-out property <string> current-view: "tasks";
|
||||
in-out property <bool> sidebar-collapsed: false;
|
||||
in-out property <bool> dialog-open: false;
|
||||
in-out property <[ToastMessage]> toasts: [];
|
||||
|
||||
// Sidebar navigation items
|
||||
property <[NavItem]> nav-items: [
|
||||
{ icon: "📝", label: "Tasks", active: current-view == "tasks" },
|
||||
{ icon: "🎨", label: "Components", active: current-view == "components" },
|
||||
{ icon: "⚙", label: "Settings", active: current-view == "settings" },
|
||||
];
|
||||
|
||||
// Callbacks
|
||||
callback add-task(string);
|
||||
callback show-toast(string, string);
|
||||
|
||||
// Main layout
|
||||
VerticalLayout {
|
||||
// Top bar
|
||||
Rectangle {
|
||||
height: 60px;
|
||||
background: Theme.colors.card;
|
||||
|
||||
Rectangle {
|
||||
y: parent.height - 1px;
|
||||
height: 1px;
|
||||
background: Theme.colors.border;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
padding: SpacingSystem.spacing.s4;
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
alignment: space-between;
|
||||
|
||||
// Left: Title and badge
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
alignment: start;
|
||||
|
||||
Text {
|
||||
text: "📋 Task Manager";
|
||||
font-size: Typography.sizes.xl;
|
||||
font-weight: Typography.weights.bold;
|
||||
color: Theme.colors.foreground;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Badge {
|
||||
text: "Demo";
|
||||
variant: "secondary";
|
||||
}
|
||||
}
|
||||
|
||||
// Right: Theme toggle button
|
||||
Button {
|
||||
text: Theme.is-dark-mode ? "☀ Light" : "🌙 Dark";
|
||||
variant: "outline";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
Theme.toggle-theme();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main content area
|
||||
HorizontalLayout {
|
||||
// Sidebar
|
||||
Sidebar {
|
||||
items: nav-items;
|
||||
collapsed: sidebar-collapsed;
|
||||
|
||||
item-clicked(index) => {
|
||||
if index == 0 { current-view = "tasks"; }
|
||||
else if index == 1 { current-view = "components"; }
|
||||
else if index == 2 { current-view = "settings"; }
|
||||
}
|
||||
|
||||
toggle-collapsed => {
|
||||
sidebar-collapsed = !sidebar-collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
// Main content area
|
||||
Rectangle {
|
||||
background: Theme.colors.background;
|
||||
|
||||
Flickable {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
viewport-height: content-layout.preferred-height + 100px;
|
||||
|
||||
content-layout := VerticalLayout {
|
||||
padding: SpacingSystem.spacing.s6;
|
||||
spacing: SpacingSystem.spacing.s6;
|
||||
|
||||
// Tasks view
|
||||
if current-view == "tasks": VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s6;
|
||||
|
||||
// Add task card
|
||||
Card {
|
||||
CardHeader {
|
||||
CardTitle { text: "Add New Task"; }
|
||||
CardDescription { text: "Create a new task to track your work"; }
|
||||
}
|
||||
|
||||
CardContent {
|
||||
Input {
|
||||
value <=> new-task-title;
|
||||
placeholder: "Enter task title...";
|
||||
|
||||
accepted(text) => {
|
||||
if text != "" {
|
||||
root.add-task(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardFooter {
|
||||
Button {
|
||||
text: "Add Task";
|
||||
|
||||
clicked => {
|
||||
if new-task-title != "" {
|
||||
root.add-task(new-task-title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Clear";
|
||||
variant: "outline";
|
||||
|
||||
clicked => {
|
||||
new-task-title = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example task 1
|
||||
Card {
|
||||
hoverable: true;
|
||||
|
||||
CardHeader {
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: space-between;
|
||||
|
||||
CardTitle { text: "Design UI Components"; }
|
||||
Badge { text: "In Progress"; variant: "default"; }
|
||||
}
|
||||
}
|
||||
|
||||
CardContent {
|
||||
CardDescription {
|
||||
text: "Create a comprehensive set of UI components following shadcn design principles with proper theming support.";
|
||||
}
|
||||
}
|
||||
|
||||
CardFooter {
|
||||
Button {
|
||||
text: "Complete";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.show-toast("Task completed!", "success");
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Delete";
|
||||
variant: "destructive";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
dialog-open = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example task 2
|
||||
Card {
|
||||
hoverable: true;
|
||||
|
||||
CardHeader {
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: space-between;
|
||||
|
||||
CardTitle { text: "Write Documentation"; }
|
||||
Badge { text: "Todo"; variant: "secondary"; }
|
||||
}
|
||||
}
|
||||
|
||||
CardContent {
|
||||
CardDescription {
|
||||
text: "Document all components with usage examples and API references for developers.";
|
||||
}
|
||||
}
|
||||
|
||||
CardFooter {
|
||||
Button {
|
||||
text: "Start";
|
||||
variant: "outline";
|
||||
size: "sm";
|
||||
|
||||
clicked => {
|
||||
root.show-toast("Task started!", "default");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example task 3
|
||||
Card {
|
||||
hoverable: true;
|
||||
|
||||
CardHeader {
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: space-between;
|
||||
|
||||
CardTitle { text: "Test Components"; }
|
||||
Badge { text: "Done"; variant: "outline"; }
|
||||
}
|
||||
}
|
||||
|
||||
CardContent {
|
||||
CardDescription {
|
||||
text: "Thoroughly test all components across different themes and screen sizes.";
|
||||
}
|
||||
}
|
||||
|
||||
CardFooter {
|
||||
Button {
|
||||
text: "View Details";
|
||||
variant: "ghost";
|
||||
size: "sm";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Components showcase view
|
||||
if current-view == "components": VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s6;
|
||||
|
||||
// Button showcase
|
||||
Card {
|
||||
CardHeader {
|
||||
CardTitle { text: "Button Component"; }
|
||||
CardDescription { text: "Various button styles and sizes"; }
|
||||
}
|
||||
|
||||
CardContent {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
// Variants
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Button { text: "Default"; }
|
||||
Button { text: "Secondary"; variant: "secondary"; }
|
||||
Button { text: "Outline"; variant: "outline"; }
|
||||
Button { text: "Ghost"; variant: "ghost"; }
|
||||
Button { text: "Destructive"; variant: "destructive"; }
|
||||
}
|
||||
|
||||
// Sizes
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
alignment: center;
|
||||
|
||||
Button { text: "Small"; size: "sm"; }
|
||||
Button { text: "Medium"; size: "md"; }
|
||||
Button { text: "Large"; size: "lg"; }
|
||||
}
|
||||
|
||||
// Disabled
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Button { text: "Disabled"; disabled: true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Badge showcase
|
||||
Card {
|
||||
CardHeader {
|
||||
CardTitle { text: "Badge Component"; }
|
||||
CardDescription { text: "Status indicators and labels"; }
|
||||
}
|
||||
|
||||
CardContent {
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Badge { text: "Default"; }
|
||||
Badge { text: "Secondary"; variant: "secondary"; }
|
||||
Badge { text: "Destructive"; variant: "destructive"; }
|
||||
Badge { text: "Outline"; variant: "outline"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input showcase
|
||||
Card {
|
||||
CardHeader {
|
||||
CardTitle { text: "Input Component"; }
|
||||
CardDescription { text: "Text input fields with various states"; }
|
||||
}
|
||||
|
||||
CardContent {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
Input { placeholder: "Normal input..."; }
|
||||
Input { placeholder: "Disabled input..."; disabled: true; }
|
||||
Input { placeholder: "Error input..."; error: true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Item showcase
|
||||
Card {
|
||||
CardHeader {
|
||||
CardTitle { text: "Item Component"; }
|
||||
CardDescription { text: "List items with icons and descriptions"; }
|
||||
}
|
||||
|
||||
CardContent {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Item {
|
||||
icon: "📁";
|
||||
title: "Project Files";
|
||||
description: "All your project documents and resources";
|
||||
}
|
||||
|
||||
Item {
|
||||
icon: "⭐";
|
||||
title: "Favorites";
|
||||
description: "Your starred items for quick access";
|
||||
selected: true;
|
||||
}
|
||||
|
||||
Item {
|
||||
icon: "🗑";
|
||||
title: "Trash";
|
||||
description: "Recently deleted items";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dialog and Toast showcase
|
||||
Card {
|
||||
CardHeader {
|
||||
CardTitle { text: "Dialog & Toast"; }
|
||||
CardDescription { text: "Modal dialogs and notification toasts"; }
|
||||
}
|
||||
|
||||
CardContent {
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Button {
|
||||
text: "Open Dialog";
|
||||
|
||||
clicked => {
|
||||
dialog-open = true;
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Show Success Toast";
|
||||
variant: "outline";
|
||||
|
||||
clicked => {
|
||||
root.show-toast("Operation successful!", "success");
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Show Error Toast";
|
||||
variant: "outline";
|
||||
|
||||
clicked => {
|
||||
root.show-toast("Something went wrong!", "error");
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Show Warning Toast";
|
||||
variant: "outline";
|
||||
|
||||
clicked => {
|
||||
root.show-toast("Please be careful!", "warning");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings view
|
||||
if current-view == "settings": VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s6;
|
||||
|
||||
Card {
|
||||
CardHeader {
|
||||
CardTitle { text: "Appearance"; }
|
||||
CardDescription { text: "Customize the look and feel of the application"; }
|
||||
}
|
||||
|
||||
CardContent {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s4;
|
||||
alignment: space-between;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s1;
|
||||
|
||||
Text {
|
||||
text: "Theme";
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.medium;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Choose between light and dark mode";
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: Theme.is-dark-mode ? "Dark Mode" : "Light Mode";
|
||||
variant: "outline";
|
||||
|
||||
clicked => {
|
||||
Theme.toggle-theme();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
CardHeader {
|
||||
CardTitle { text: "About"; }
|
||||
CardDescription { text: "Information about this demo application"; }
|
||||
}
|
||||
|
||||
CardContent {
|
||||
VerticalLayout {
|
||||
spacing: SpacingSystem.spacing.s3;
|
||||
|
||||
Text {
|
||||
text: "Slint Shadcn UI Component Library";
|
||||
font-size: Typography.sizes.base;
|
||||
font-weight: Typography.weights.semibold;
|
||||
color: Theme.colors.foreground;
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "A comprehensive set of UI components inspired by shadcn/ui, built with Slint. Features include theme support, smooth animations, and a clean, modern design.";
|
||||
font-size: Typography.sizes.sm;
|
||||
color: Theme.colors.muted-foreground;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: SpacingSystem.spacing.s2;
|
||||
|
||||
Badge { text: "v1.0.0"; variant: "secondary"; }
|
||||
Badge { text: "Slint 1.14"; variant: "outline"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dialog overlay - inline implementation
|
||||
Dialog {
|
||||
open: dialog-open;
|
||||
title: "Confirm Delete";
|
||||
description: "Are you sure you want to delete this task? This action cannot be undone.";
|
||||
|
||||
close => {
|
||||
dialog-open = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Toast container
|
||||
Rectangle {
|
||||
x: parent.width - 390px;
|
||||
y: 20px;
|
||||
width: 370px;
|
||||
|
||||
ToastContainer {
|
||||
toasts: toasts;
|
||||
|
||||
toast-dismissed(index) => {
|
||||
// Will be handled by Rust
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
ui/theme/colors.slint
Normal file
75
ui/theme/colors.slint
Normal file
@@ -0,0 +1,75 @@
|
||||
// Shadcn-style Color System
|
||||
// Based on shadcn/ui zinc theme with light/dark mode support
|
||||
|
||||
// Color palette structure
|
||||
export struct ColorPalette {
|
||||
background: color,
|
||||
foreground: color,
|
||||
card: color,
|
||||
card-foreground: color,
|
||||
popover: color,
|
||||
popover-foreground: color,
|
||||
primary: color,
|
||||
primary-foreground: color,
|
||||
secondary: color,
|
||||
secondary-foreground: color,
|
||||
muted: color,
|
||||
muted-foreground: color,
|
||||
accent: color,
|
||||
accent-foreground: color,
|
||||
destructive: color,
|
||||
destructive-foreground: color,
|
||||
border: color,
|
||||
input: color,
|
||||
ring: color,
|
||||
}
|
||||
|
||||
// Light mode colors - shadcn zinc theme
|
||||
export global LightColors {
|
||||
out property <ColorPalette> palette: {
|
||||
background: #ffffff,
|
||||
foreground: #09090b,
|
||||
card: #ffffff,
|
||||
card-foreground: #09090b,
|
||||
popover: #ffffff,
|
||||
popover-foreground: #09090b,
|
||||
primary: #18181b,
|
||||
primary-foreground: #fafafa,
|
||||
secondary: #f4f4f5,
|
||||
secondary-foreground: #18181b,
|
||||
muted: #f4f4f5,
|
||||
muted-foreground: #71717a,
|
||||
accent: #f4f4f5,
|
||||
accent-foreground: #18181b,
|
||||
destructive: #ef4444,
|
||||
destructive-foreground: #fafafa,
|
||||
border: #e4e4e7,
|
||||
input: #e4e4e7,
|
||||
ring: #18181b,
|
||||
};
|
||||
}
|
||||
|
||||
// Dark mode colors - shadcn zinc theme
|
||||
export global DarkColors {
|
||||
out property <ColorPalette> palette: {
|
||||
background: #09090b,
|
||||
foreground: #fafafa,
|
||||
card: #09090b,
|
||||
card-foreground: #fafafa,
|
||||
popover: #09090b,
|
||||
popover-foreground: #fafafa,
|
||||
primary: #fafafa,
|
||||
primary-foreground: #18181b,
|
||||
secondary: #27272a,
|
||||
secondary-foreground: #fafafa,
|
||||
muted: #27272a,
|
||||
muted-foreground: #a1a1aa,
|
||||
accent: #27272a,
|
||||
accent-foreground: #fafafa,
|
||||
destructive: #7f1d1d,
|
||||
destructive-foreground: #fafafa,
|
||||
border: #27272a,
|
||||
input: #27272a,
|
||||
ring: #d4d4d8,
|
||||
};
|
||||
}
|
||||
55
ui/theme/spacing.slint
Normal file
55
ui/theme/spacing.slint
Normal file
@@ -0,0 +1,55 @@
|
||||
// Spacing and Border Radius System
|
||||
// Consistent spacing scale following shadcn design principles
|
||||
|
||||
// Spacing scale (px)
|
||||
export struct Spacing {
|
||||
s0: length,
|
||||
s1: length,
|
||||
s2: length,
|
||||
s3: length,
|
||||
s4: length,
|
||||
s6: length,
|
||||
s8: length,
|
||||
s12: length,
|
||||
s16: length,
|
||||
s24: length,
|
||||
s32: length,
|
||||
}
|
||||
|
||||
// Border radius scale (px)
|
||||
export struct BorderRadius {
|
||||
sm: length,
|
||||
md: length,
|
||||
lg: length,
|
||||
xl: length,
|
||||
xl2: length,
|
||||
full: length,
|
||||
}
|
||||
|
||||
// Global spacing configuration
|
||||
export global SpacingSystem {
|
||||
// Spacing values
|
||||
out property <Spacing> spacing: {
|
||||
s0: 0px,
|
||||
s1: 4px,
|
||||
s2: 8px,
|
||||
s3: 12px,
|
||||
s4: 16px,
|
||||
s6: 24px,
|
||||
s8: 32px,
|
||||
s12: 48px,
|
||||
s16: 64px,
|
||||
s24: 96px,
|
||||
s32: 128px,
|
||||
};
|
||||
|
||||
// Border radius values
|
||||
out property <BorderRadius> radius: {
|
||||
sm: 4px,
|
||||
md: 6px,
|
||||
lg: 8px,
|
||||
xl: 12px,
|
||||
xl2: 16px,
|
||||
full: 9999px,
|
||||
};
|
||||
}
|
||||
26
ui/theme/theme.slint
Normal file
26
ui/theme/theme.slint
Normal file
@@ -0,0 +1,26 @@
|
||||
// Theme Manager
|
||||
// Global theme state with light/dark mode support
|
||||
|
||||
import { ColorPalette, LightColors, DarkColors } from "colors.slint";
|
||||
import { Typography } from "typography.slint";
|
||||
import { SpacingSystem } from "spacing.slint";
|
||||
|
||||
// Global theme manager (singleton)
|
||||
export global Theme {
|
||||
// Theme state
|
||||
in-out property <bool> is-dark-mode: false;
|
||||
|
||||
// Current active color palette (reactive)
|
||||
out property <ColorPalette> colors: is-dark-mode ?
|
||||
DarkColors.palette : LightColors.palette;
|
||||
|
||||
// Theme toggle callback
|
||||
callback toggle-theme();
|
||||
|
||||
toggle-theme => {
|
||||
is-dark-mode = !is-dark-mode;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export for convenience
|
||||
export { Typography, SpacingSystem }
|
||||
59
ui/theme/typography.slint
Normal file
59
ui/theme/typography.slint
Normal file
@@ -0,0 +1,59 @@
|
||||
// Typography System
|
||||
// Font sizes, weights, and line heights following shadcn design principles
|
||||
|
||||
// Font size scale
|
||||
export struct FontSizes {
|
||||
xs: length,
|
||||
sm: length,
|
||||
base: length,
|
||||
lg: length,
|
||||
xl: length,
|
||||
xl2: length,
|
||||
xl3: length,
|
||||
xl4: length,
|
||||
}
|
||||
|
||||
// Font weight scale
|
||||
export struct FontWeights {
|
||||
normal: int,
|
||||
medium: int,
|
||||
semibold: int,
|
||||
bold: int,
|
||||
}
|
||||
|
||||
// Line height scale
|
||||
export struct LineHeights {
|
||||
tight: float,
|
||||
normal: float,
|
||||
relaxed: float,
|
||||
}
|
||||
|
||||
// Global typography configuration
|
||||
export global Typography {
|
||||
// Font sizes (px)
|
||||
out property <FontSizes> sizes: {
|
||||
xs: 12px,
|
||||
sm: 14px,
|
||||
base: 16px,
|
||||
lg: 18px,
|
||||
xl: 20px,
|
||||
xl2: 24px,
|
||||
xl3: 30px,
|
||||
xl4: 36px,
|
||||
};
|
||||
|
||||
// Font weights
|
||||
out property <FontWeights> weights: {
|
||||
normal: 400,
|
||||
medium: 500,
|
||||
semibold: 600,
|
||||
bold: 700,
|
||||
};
|
||||
|
||||
// Line heights (relative)
|
||||
out property <LineHeights> line-heights: {
|
||||
tight: 1.25,
|
||||
normal: 1.5,
|
||||
relaxed: 1.75,
|
||||
};
|
||||
}
|
||||
25
ui/utils/animations.slint
Normal file
25
ui/utils/animations.slint
Normal file
@@ -0,0 +1,25 @@
|
||||
// Animation System
|
||||
// Standard animation durations and easing functions
|
||||
|
||||
// Animation duration scale
|
||||
export struct AnimationDurations {
|
||||
fast: duration,
|
||||
normal: duration,
|
||||
slow: duration,
|
||||
}
|
||||
|
||||
// Global animation configuration
|
||||
export global Animations {
|
||||
// Duration presets
|
||||
out property <AnimationDurations> durations: {
|
||||
fast: 150ms,
|
||||
normal: 200ms,
|
||||
slow: 300ms,
|
||||
};
|
||||
|
||||
// Easing functions (cubic-bezier)
|
||||
out property <easing> ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
out property <easing> ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
out property <easing> ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
out property <easing> ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
Reference in New Issue
Block a user