cronixui 1.0.6 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -5
- package/package.json +20 -5
- package/packages/flutter/lib/cronixui.dart +41 -0
- package/packages/flutter/lib/src/tokens/colors.dart +34 -0
- package/packages/flutter/lib/src/tokens/spacing.dart +54 -0
- package/packages/flutter/lib/src/tokens/theme.dart +174 -0
- package/packages/flutter/lib/src/widgets/cn_accordion.dart +254 -0
- package/packages/flutter/lib/src/widgets/cn_alert.dart +137 -0
- package/packages/flutter/lib/src/widgets/cn_avatar.dart +98 -0
- package/packages/flutter/lib/src/widgets/cn_badge.dart +80 -0
- package/packages/flutter/lib/src/widgets/cn_breadcrumb.dart +88 -0
- package/packages/flutter/lib/src/widgets/cn_button.dart +137 -0
- package/packages/flutter/lib/src/widgets/cn_card.dart +99 -0
- package/packages/flutter/lib/src/widgets/cn_checkbox.dart +77 -0
- package/packages/flutter/lib/src/widgets/cn_command_palette.dart +299 -0
- package/packages/flutter/lib/src/widgets/cn_container.dart +131 -0
- package/packages/flutter/lib/src/widgets/cn_dropdown.dart +149 -0
- package/packages/flutter/lib/src/widgets/cn_file_input.dart +113 -0
- package/packages/flutter/lib/src/widgets/cn_footer.dart +108 -0
- package/packages/flutter/lib/src/widgets/cn_header.dart +173 -0
- package/packages/flutter/lib/src/widgets/cn_input.dart +142 -0
- package/packages/flutter/lib/src/widgets/cn_list.dart +150 -0
- package/packages/flutter/lib/src/widgets/cn_modal.dart +213 -0
- package/packages/flutter/lib/src/widgets/cn_nav.dart +157 -0
- package/packages/flutter/lib/src/widgets/cn_pagination.dart +193 -0
- package/packages/flutter/lib/src/widgets/cn_progress.dart +146 -0
- package/packages/flutter/lib/src/widgets/cn_radio.dart +133 -0
- package/packages/flutter/lib/src/widgets/cn_search.dart +183 -0
- package/packages/flutter/lib/src/widgets/cn_select.dart +244 -0
- package/packages/flutter/lib/src/widgets/cn_sidebar.dart +207 -0
- package/packages/flutter/lib/src/widgets/cn_skeleton.dart +136 -0
- package/packages/flutter/lib/src/widgets/cn_slider.dart +141 -0
- package/packages/flutter/lib/src/widgets/cn_spinner.dart +85 -0
- package/packages/flutter/lib/src/widgets/cn_stat.dart +135 -0
- package/packages/flutter/lib/src/widgets/cn_table.dart +136 -0
- package/packages/flutter/lib/src/widgets/cn_tabs.dart +229 -0
- package/packages/flutter/lib/src/widgets/cn_tag.dart +185 -0
- package/packages/flutter/lib/src/widgets/cn_textarea.dart +143 -0
- package/packages/flutter/lib/src/widgets/cn_toast.dart +121 -0
- package/packages/flutter/lib/src/widgets/cn_toggle.dart +78 -0
- package/packages/flutter/lib/src/widgets/cn_tooltip.dart +118 -0
- package/packages/flutter/pubspec.yaml +20 -0
- package/packages/go/cronixui/cronixui.go +784 -237
- package/packages/go/cronixui/go.mod +32 -0
- package/packages/go/cronixui/go.sum +666 -0
- package/packages/python/cronixui/__init__.py +59 -3
- package/packages/python/cronixui/alert.py +61 -0
- package/packages/python/cronixui/avatar.py +50 -0
- package/packages/python/cronixui/badge.py +46 -0
- package/packages/python/cronixui/button.py +64 -0
- package/packages/python/cronixui/card.py +62 -0
- package/packages/python/cronixui/form.py +255 -0
- package/packages/python/cronixui/layout.py +143 -0
- package/packages/python/cronixui/list.py +51 -0
- package/packages/python/cronixui/loading.py +36 -0
- package/packages/python/cronixui/progress.py +90 -0
- package/packages/python/cronixui/table.py +48 -0
- package/packages/python/cronixui/tooltip.py +28 -0
- package/packages/react/src/components/Accordion.tsx +82 -0
- package/packages/react/src/components/Alert.tsx +80 -0
- package/packages/react/src/components/Avatar.tsx +54 -0
- package/packages/react/src/components/Badge.tsx +32 -0
- package/packages/react/src/components/Breadcrumb.tsx +50 -0
- package/packages/react/src/components/Button.tsx +47 -0
- package/packages/react/src/components/Card.tsx +69 -0
- package/packages/react/src/components/Checkbox.tsx +30 -0
- package/packages/react/src/components/CommandPalette.tsx +131 -0
- package/packages/react/src/components/Container.tsx +26 -0
- package/packages/react/src/components/Dropdown.tsx +88 -0
- package/packages/react/src/components/FileInput.tsx +86 -0
- package/packages/react/src/components/Footer.tsx +36 -0
- package/packages/react/src/components/FormGroup.tsx +36 -0
- package/packages/react/src/components/Header.tsx +29 -0
- package/packages/react/src/components/Input.tsx +54 -0
- package/packages/react/src/components/List.tsx +55 -0
- package/packages/react/src/components/Modal.tsx +89 -0
- package/packages/react/src/components/Nav.tsx +63 -0
- package/packages/react/src/components/Pagination.tsx +107 -0
- package/packages/react/src/components/Progress.tsx +49 -0
- package/packages/react/src/components/Radio.tsx +64 -0
- package/packages/react/src/components/Search.tsx +95 -0
- package/packages/react/src/components/Select.tsx +41 -0
- package/packages/react/src/components/Sidebar.tsx +64 -0
- package/packages/react/src/components/Skeleton.tsx +39 -0
- package/packages/react/src/components/Slider.tsx +32 -0
- package/packages/react/src/components/Spinner.tsx +24 -0
- package/packages/react/src/components/Stack.tsx +69 -0
- package/packages/react/src/components/Stat.tsx +35 -0
- package/packages/react/src/components/Table.tsx +90 -0
- package/packages/react/src/components/Tabs.tsx +85 -0
- package/packages/react/src/components/Tag.tsx +30 -0
- package/packages/react/src/components/Textarea.tsx +21 -0
- package/packages/react/src/components/Toast.tsx +134 -0
- package/packages/react/src/components/Toggle.tsx +58 -0
- package/packages/react/src/components/Tooltip.tsx +31 -0
- package/packages/react/src/components/Typography.tsx +66 -0
- package/packages/react/src/index.ts +40 -0
- package/packages/react/src/styles.css +2039 -0
- package/packages/react/src/tokens/index.ts +94 -0
- package/packages/rust/cronixui/src/colors.rs +135 -0
- package/packages/rust/cronixui/src/components/accordion.rs +47 -0
- package/packages/rust/cronixui/src/components/alert.rs +95 -0
- package/packages/rust/cronixui/src/components/avatar.rs +85 -0
- package/packages/rust/cronixui/src/components/badge.rs +35 -0
- package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -0
- package/packages/rust/cronixui/src/components/button.rs +70 -0
- package/packages/rust/cronixui/src/components/card.rs +259 -0
- package/packages/rust/cronixui/src/components/command_palette.rs +254 -0
- package/packages/rust/cronixui/src/components/dropdown.rs +179 -0
- package/packages/rust/cronixui/src/components/file_input.rs +74 -0
- package/packages/rust/cronixui/src/components/input.rs +21 -0
- package/packages/rust/cronixui/src/components/list.rs +38 -0
- package/packages/rust/cronixui/src/components/mod.rs +51 -0
- package/packages/rust/cronixui/src/{modal.rs → components/modal.rs} +15 -1
- package/packages/rust/cronixui/src/components/nav.rs +19 -0
- package/packages/rust/cronixui/src/{pagination.rs → components/pagination.rs} +14 -13
- package/packages/rust/cronixui/src/components/progress.rs +50 -0
- package/packages/rust/cronixui/src/components/search.rs +185 -0
- package/packages/rust/cronixui/src/components/skeleton.rs +63 -0
- package/packages/rust/cronixui/src/components/spinner.rs +21 -0
- package/packages/rust/cronixui/src/components/table.rs +56 -0
- package/packages/rust/cronixui/src/components/tabs.rs +43 -0
- package/packages/rust/cronixui/src/components/toast.rs +69 -0
- package/packages/rust/cronixui/src/{toggle.rs → components/toggle.rs} +7 -5
- package/packages/rust/cronixui/src/components/tooltip.rs +11 -0
- package/packages/rust/cronixui/src/lib.rs +111 -64
- package/packages/rust/cronixui/src/tokens.rs +97 -127
- package/packages/web/src/variables.css +81 -81
- package/packages/go/cronixui/tokens.go +0 -129
- package/packages/python/cronixui/pyproject.toml +0 -11
- package/packages/react/src/components/Accordion.jsx +0 -50
- package/packages/react/src/components/Alert.jsx +0 -62
- package/packages/react/src/components/Avatar.jsx +0 -34
- package/packages/react/src/components/Badge.jsx +0 -15
- package/packages/react/src/components/Breadcrumb.jsx +0 -27
- package/packages/react/src/components/Button.jsx +0 -21
- package/packages/react/src/components/Card.jsx +0 -23
- package/packages/react/src/components/Checkbox.jsx +0 -27
- package/packages/react/src/components/CommandPalette.jsx +0 -93
- package/packages/react/src/components/Dropdown.jsx +0 -48
- package/packages/react/src/components/FileInput.jsx +0 -44
- package/packages/react/src/components/Input.jsx +0 -22
- package/packages/react/src/components/List.jsx +0 -29
- package/packages/react/src/components/Modal.jsx +0 -65
- package/packages/react/src/components/Nav.jsx +0 -50
- package/packages/react/src/components/Pagination.jsx +0 -81
- package/packages/react/src/components/Progress.jsx +0 -23
- package/packages/react/src/components/Radio.jsx +0 -50
- package/packages/react/src/components/Search.jsx +0 -70
- package/packages/react/src/components/Select.jsx +0 -33
- package/packages/react/src/components/Skeleton.jsx +0 -15
- package/packages/react/src/components/Slider.jsx +0 -29
- package/packages/react/src/components/Spinner.jsx +0 -5
- package/packages/react/src/components/Stat.jsx +0 -19
- package/packages/react/src/components/Table.jsx +0 -48
- package/packages/react/src/components/Tabs.jsx +0 -65
- package/packages/react/src/components/Tag.jsx +0 -19
- package/packages/react/src/components/Textarea.jsx +0 -17
- package/packages/react/src/components/Toast.jsx +0 -78
- package/packages/react/src/components/Toggle.jsx +0 -34
- package/packages/react/src/components/Tooltip.jsx +0 -12
- package/packages/react/src/index.d.ts +0 -103
- package/packages/react/src/index.js +0 -33
- package/packages/rust/cronixui/src/accordion.rs +0 -49
- package/packages/rust/cronixui/src/command_palette.rs +0 -62
- package/packages/rust/cronixui/src/dropdown.rs +0 -31
- package/packages/rust/cronixui/src/search.rs +0 -49
- package/packages/rust/cronixui/src/tabs.rs +0 -23
- package/packages/rust/cronixui/src/toast.rs +0 -70
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//! File input component
|
|
2
|
+
|
|
3
|
+
use egui::*;
|
|
4
|
+
use crate::{colors::*, tokens::*};
|
|
5
|
+
|
|
6
|
+
pub struct FileInput {
|
|
7
|
+
pub accept: String,
|
|
8
|
+
pub multiple: bool,
|
|
9
|
+
pub files: Vec<String>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
impl FileInput {
|
|
13
|
+
pub fn new() -> Self {
|
|
14
|
+
Self { accept: String::new(), multiple: false, files: Vec::new() }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pub fn accept(mut self, accept: impl Into<String>) -> Self {
|
|
18
|
+
self.accept = accept.into();
|
|
19
|
+
self
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub fn multiple(mut self) -> Self {
|
|
23
|
+
self.multiple = true;
|
|
24
|
+
self
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn show(&mut self, ui: &mut Ui) -> Response {
|
|
28
|
+
let colors = Colors::default();
|
|
29
|
+
|
|
30
|
+
egui::Frame::none()
|
|
31
|
+
.fill(colors.surface)
|
|
32
|
+
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
33
|
+
.rounding(Rounding::same(tokens::RADIUS))
|
|
34
|
+
.inner_margin(tokens::SPACE_4)
|
|
35
|
+
.show(ui, |ui| {
|
|
36
|
+
ui.vertical(|ui| {
|
|
37
|
+
ui.label(
|
|
38
|
+
RichText::new("📁 Drop files here or click to browse")
|
|
39
|
+
.size(tokens::FONT_SIZE_BASE)
|
|
40
|
+
.color(colors.text_muted),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if ui.button_primary("Browse files").clicked() {
|
|
44
|
+
// Note: actual file selection requires platform-specific code
|
|
45
|
+
// This is a UI placeholder
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if !self.files.is_empty() {
|
|
49
|
+
ui.add_space(tokens::SPACE_2);
|
|
50
|
+
ui.separator();
|
|
51
|
+
ui.add_space(tokens::SPACE_2);
|
|
52
|
+
|
|
53
|
+
for file in &self.files {
|
|
54
|
+
ui.horizontal(|ui| {
|
|
55
|
+
ui.label("📄");
|
|
56
|
+
ui.label(
|
|
57
|
+
RichText::new(file)
|
|
58
|
+
.size(tokens::FONT_SIZE_SM)
|
|
59
|
+
.color(colors.text),
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
})
|
|
66
|
+
.response
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl Default for FileInput {
|
|
71
|
+
fn default() -> Self {
|
|
72
|
+
Self::new()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//! Input component
|
|
2
|
+
|
|
3
|
+
use egui::{TextEdit, Ui};
|
|
4
|
+
|
|
5
|
+
use crate::tokens::*;
|
|
6
|
+
|
|
7
|
+
pub fn input(ui: &mut Ui, text: &mut String, placeholder: &str) -> egui::Response {
|
|
8
|
+
ui.add(TextEdit::singleline(text).hint_text(placeholder).desired_width(f32::INFINITY))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pub fn textarea(ui: &mut Ui, text: &mut String, placeholder: &str) -> egui::Response {
|
|
12
|
+
ui.add(TextEdit::multiline(text).hint_text(placeholder))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub fn password(ui: &mut Ui, text: &mut String, placeholder: &str) -> egui::Response {
|
|
16
|
+
ui.add(TextEdit::singleline(text).hint_text(placeholder).password(true))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub fn search(ui: &mut Ui, text: &mut String) -> egui::Response {
|
|
20
|
+
ui.add(TextEdit::singleline(text).hint_text("Search...").desired_width(f32::INFINITY))
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//! List component
|
|
2
|
+
|
|
3
|
+
pub struct ListItem {
|
|
4
|
+
pub title: String,
|
|
5
|
+
pub subtitle: Option<String>,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
impl ListItem {
|
|
9
|
+
pub fn new(title: impl Into<String>) -> Self {
|
|
10
|
+
Self { title: title.into(), subtitle: None }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
|
|
14
|
+
self.subtitle = Some(subtitle.into());
|
|
15
|
+
self
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub struct List {
|
|
20
|
+
pub items: Vec<ListItem>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl List {
|
|
24
|
+
pub fn new() -> Self {
|
|
25
|
+
Self { items: Vec::new() }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn item(mut self, item: ListItem) -> Self {
|
|
29
|
+
self.items.push(item);
|
|
30
|
+
self
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl Default for List {
|
|
35
|
+
fn default() -> Self {
|
|
36
|
+
Self::new()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//! CronixUI Components for egui
|
|
2
|
+
|
|
3
|
+
pub mod button;
|
|
4
|
+
pub mod card;
|
|
5
|
+
pub mod input;
|
|
6
|
+
pub mod toggle;
|
|
7
|
+
pub mod modal;
|
|
8
|
+
pub mod tabs;
|
|
9
|
+
pub mod accordion;
|
|
10
|
+
pub mod toast;
|
|
11
|
+
pub mod badge;
|
|
12
|
+
pub mod avatar;
|
|
13
|
+
pub mod progress;
|
|
14
|
+
pub mod spinner;
|
|
15
|
+
pub mod skeleton;
|
|
16
|
+
pub mod table;
|
|
17
|
+
pub mod list;
|
|
18
|
+
pub mod nav;
|
|
19
|
+
pub mod breadcrumb;
|
|
20
|
+
pub mod tooltip;
|
|
21
|
+
pub mod dropdown;
|
|
22
|
+
pub mod pagination;
|
|
23
|
+
pub mod file_input;
|
|
24
|
+
pub mod search;
|
|
25
|
+
pub mod command_palette;
|
|
26
|
+
pub mod alert;
|
|
27
|
+
|
|
28
|
+
pub use button::*;
|
|
29
|
+
pub use card::*;
|
|
30
|
+
pub use input::*;
|
|
31
|
+
pub use toggle::*;
|
|
32
|
+
pub use modal::*;
|
|
33
|
+
pub use tabs::*;
|
|
34
|
+
pub use accordion::*;
|
|
35
|
+
pub use toast::*;
|
|
36
|
+
pub use badge::*;
|
|
37
|
+
pub use avatar::*;
|
|
38
|
+
pub use progress::*;
|
|
39
|
+
pub use spinner::*;
|
|
40
|
+
pub use skeleton::*;
|
|
41
|
+
pub use table::*;
|
|
42
|
+
pub use list::*;
|
|
43
|
+
pub use nav::*;
|
|
44
|
+
pub use breadcrumb::*;
|
|
45
|
+
pub use tooltip::*;
|
|
46
|
+
pub use dropdown::*;
|
|
47
|
+
pub use pagination::*;
|
|
48
|
+
pub use file_input::*;
|
|
49
|
+
pub use search::*;
|
|
50
|
+
pub use command_palette::*;
|
|
51
|
+
pub use alert::*;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
//! Modal component
|
|
2
|
+
|
|
3
|
+
use egui::Ui;
|
|
4
|
+
|
|
1
5
|
pub struct Modal {
|
|
2
|
-
open: bool,
|
|
6
|
+
pub open: bool,
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
impl Modal {
|
|
@@ -18,6 +22,16 @@ impl Modal {
|
|
|
18
22
|
pub fn is_open(&self) -> bool {
|
|
19
23
|
self.open
|
|
20
24
|
}
|
|
25
|
+
|
|
26
|
+
pub fn show<F: FnOnce(&mut Ui)>(&mut self, ui: &mut Ui, add_contents: F) {
|
|
27
|
+
if self.open {
|
|
28
|
+
egui::Area::new(egui::Id::new("modal"))
|
|
29
|
+
.interactable(true)
|
|
30
|
+
.show(ui.ctx(), |ui| {
|
|
31
|
+
add_contents(ui);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
impl Default for Modal {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//! Navigation component
|
|
2
|
+
|
|
3
|
+
pub struct Nav {
|
|
4
|
+
pub items: Vec<String>,
|
|
5
|
+
pub active: usize,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
impl Nav {
|
|
9
|
+
pub fn new(items: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
|
10
|
+
Self {
|
|
11
|
+
items: items.into_iter().map(|s| s.into()).collect(),
|
|
12
|
+
active: 0,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn set_active(&mut self, index: usize) {
|
|
17
|
+
self.active = index.min(self.items.len().saturating_sub(1));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
//! Pagination component
|
|
2
|
+
|
|
1
3
|
pub struct Pagination {
|
|
2
|
-
total: usize,
|
|
3
|
-
current: usize,
|
|
4
|
+
pub total: usize,
|
|
5
|
+
pub current: usize,
|
|
4
6
|
}
|
|
5
7
|
|
|
6
8
|
impl Pagination {
|
|
7
|
-
pub fn new(total: usize
|
|
8
|
-
|
|
9
|
-
Self { total, current }
|
|
9
|
+
pub fn new(total: usize) -> Self {
|
|
10
|
+
Self { total, current: 1 }
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
pub fn go_to(&mut self, page: usize) {
|
|
@@ -15,14 +16,6 @@ impl Pagination {
|
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
pub fn current(&self) -> usize {
|
|
19
|
-
self.current
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
pub fn total(&self) -> usize {
|
|
23
|
-
self.total
|
|
24
|
-
}
|
|
25
|
-
|
|
26
19
|
pub fn next(&mut self) {
|
|
27
20
|
if self.current < self.total {
|
|
28
21
|
self.current += 1;
|
|
@@ -34,4 +27,12 @@ impl Pagination {
|
|
|
34
27
|
self.current -= 1;
|
|
35
28
|
}
|
|
36
29
|
}
|
|
30
|
+
|
|
31
|
+
pub fn is_first(&self) -> bool {
|
|
32
|
+
self.current == 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn is_last(&self) -> bool {
|
|
36
|
+
self.current == self.total
|
|
37
|
+
}
|
|
37
38
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//! Progress component
|
|
2
|
+
|
|
3
|
+
pub struct Progress {
|
|
4
|
+
pub value: f32,
|
|
5
|
+
pub max: f32,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
impl Progress {
|
|
9
|
+
pub fn new() -> Self {
|
|
10
|
+
Self { value: 0.0, max: 100.0 }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub fn set_value(&mut self, value: f32) {
|
|
14
|
+
self.value = value.clamp(0.0, self.max);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pub fn percentage(&self) -> f32 {
|
|
18
|
+
(self.value / self.max) * 100.0
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl Default for Progress {
|
|
23
|
+
fn default() -> Self {
|
|
24
|
+
Self::new()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub struct Stat {
|
|
29
|
+
pub value: String,
|
|
30
|
+
pub label: String,
|
|
31
|
+
pub delta: Option<String>,
|
|
32
|
+
pub delta_up: bool,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl Stat {
|
|
36
|
+
pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
|
|
37
|
+
Self {
|
|
38
|
+
value: value.into(),
|
|
39
|
+
label: label.into(),
|
|
40
|
+
delta: None,
|
|
41
|
+
delta_up: true,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub fn delta(mut self, delta: impl Into<String>, up: bool) -> Self {
|
|
46
|
+
self.delta = Some(delta.into());
|
|
47
|
+
self.delta_up = up;
|
|
48
|
+
self
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
//! Search component
|
|
2
|
+
|
|
3
|
+
use egui::*;
|
|
4
|
+
use crate::{colors::*, tokens::*};
|
|
5
|
+
|
|
6
|
+
pub struct SearchItem {
|
|
7
|
+
pub title: String,
|
|
8
|
+
pub subtitle: Option<String>,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
impl SearchItem {
|
|
12
|
+
pub fn new(title: impl Into<String>) -> Self {
|
|
13
|
+
Self { title: title.into(), subtitle: None }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
|
|
17
|
+
self.subtitle = Some(subtitle.into());
|
|
18
|
+
self
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub struct Search {
|
|
23
|
+
pub items: Vec<SearchItem>,
|
|
24
|
+
pub query: String,
|
|
25
|
+
id: Id,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl Search {
|
|
29
|
+
pub fn new(id: impl Into<Id>) -> Self {
|
|
30
|
+
Self { items: Vec::new(), query: String::new(), id: id.into() }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn item(mut self, item: SearchItem) -> Self {
|
|
34
|
+
self.items.push(item);
|
|
35
|
+
self
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fn set_items(&mut self, items: Vec<SearchItem>) {
|
|
39
|
+
self.items = items;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fn filter(&self) -> Vec<&SearchItem> {
|
|
43
|
+
if self.query.is_empty() {
|
|
44
|
+
return self.items.iter().collect();
|
|
45
|
+
}
|
|
46
|
+
self.items
|
|
47
|
+
.iter()
|
|
48
|
+
.filter(|item| item.title.to_lowercase().contains(&self.query.to_lowercase()))
|
|
49
|
+
.collect()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Render the search UI with text input and results list
|
|
53
|
+
pub fn show(&mut self, ui: &mut Ui) -> Option<usize> {
|
|
54
|
+
let colors = Colors::default();
|
|
55
|
+
let mut selected = None;
|
|
56
|
+
|
|
57
|
+
// Search input field
|
|
58
|
+
ui.horizontal(|ui| {
|
|
59
|
+
ui.label("🔍");
|
|
60
|
+
let response = ui.add(
|
|
61
|
+
TextEdit::singleline(&mut self.query)
|
|
62
|
+
.hint_text("Search...")
|
|
63
|
+
.desired_width(f32::INFINITY)
|
|
64
|
+
.font(FontId::new(tokens::FONT_SIZE_BASE, FontFamily::Proportional)),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if response.has_focus() {
|
|
68
|
+
// Show results dropdown when focused
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Results list
|
|
73
|
+
let results = self.filter();
|
|
74
|
+
if !results.is_empty() {
|
|
75
|
+
egui::Frame::none()
|
|
76
|
+
.fill(colors.surface)
|
|
77
|
+
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
78
|
+
.rounding(Rounding::same(tokens::RADIUS))
|
|
79
|
+
.inner_margin(SPACE_2)
|
|
80
|
+
.show(ui, |ui| {
|
|
81
|
+
for (idx, item) in results.iter().enumerate() {
|
|
82
|
+
let response = ui.horizontal(|ui| {
|
|
83
|
+
ui.label(
|
|
84
|
+
RichText::new(&item.title)
|
|
85
|
+
.size(tokens::FONT_SIZE_BASE)
|
|
86
|
+
.color(colors.text),
|
|
87
|
+
);
|
|
88
|
+
if let Some(subtitle) = &item.subtitle {
|
|
89
|
+
ui.label(
|
|
90
|
+
RichText::new(subtitle)
|
|
91
|
+
.size(tokens::FONT_SIZE_SM)
|
|
92
|
+
.color(colors.text_muted),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if response.response.clicked() {
|
|
98
|
+
// Find original index
|
|
99
|
+
if let Some(orig_idx) = self.items.iter().position(|i| std::ptr::eq(i, *item)) {
|
|
100
|
+
selected = Some(orig_idx);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
} else if !self.query.is_empty() {
|
|
106
|
+
ui.label(
|
|
107
|
+
RichText::new("No results found")
|
|
108
|
+
.size(tokens::FONT_SIZE_SM)
|
|
109
|
+
.color(colors.text_muted),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
selected
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
impl Default for Search {
|
|
118
|
+
fn default() -> Self {
|
|
119
|
+
Self::new("default_search")
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Functional search helper
|
|
124
|
+
pub fn search(ui: &mut Ui, query: &mut String, items: &[SearchItem]) -> Vec<&SearchItem> {
|
|
125
|
+
let colors = Colors::default();
|
|
126
|
+
|
|
127
|
+
ui.add(
|
|
128
|
+
TextEdit::singleline(query)
|
|
129
|
+
.hint_text("Search...")
|
|
130
|
+
.desired_width(f32::INFINITY),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if query.is_empty() {
|
|
134
|
+
return items.iter().collect();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
items
|
|
138
|
+
.iter()
|
|
139
|
+
.filter(|item| item.title.to_lowercase().contains(&query.to_lowercase()))
|
|
140
|
+
.collect()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Render search results list
|
|
144
|
+
pub fn search_results(ui: &mut Ui, results: &[&SearchItem]) -> Option<usize> {
|
|
145
|
+
let colors = Colors::default();
|
|
146
|
+
let mut clicked = None;
|
|
147
|
+
|
|
148
|
+
if !results.is_empty() {
|
|
149
|
+
egui::Frame::none()
|
|
150
|
+
.fill(colors.surface)
|
|
151
|
+
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
152
|
+
.rounding(Rounding::same(tokens::RADIUS))
|
|
153
|
+
.inner_margin(SPACE_2)
|
|
154
|
+
.show(ui, |ui| {
|
|
155
|
+
for (idx, item) in results.iter().enumerate() {
|
|
156
|
+
let response = ui.horizontal(|ui| {
|
|
157
|
+
ui.label(
|
|
158
|
+
RichText::new(&item.title)
|
|
159
|
+
.size(tokens::FONT_SIZE_BASE)
|
|
160
|
+
.color(colors.text),
|
|
161
|
+
);
|
|
162
|
+
if let Some(subtitle) = &item.subtitle {
|
|
163
|
+
ui.label(
|
|
164
|
+
RichText::new(subtitle)
|
|
165
|
+
.size(tokens::FONT_SIZE_SM)
|
|
166
|
+
.color(colors.text_muted),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if response.response.clicked() {
|
|
172
|
+
clicked = Some(idx);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
ui.label(
|
|
178
|
+
RichText::new("No results found")
|
|
179
|
+
.size(tokens::FONT_SIZE_SM)
|
|
180
|
+
.color(colors.text_muted),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
clicked
|
|
185
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//! Skeleton loading placeholder
|
|
2
|
+
|
|
3
|
+
use egui::*;
|
|
4
|
+
|
|
5
|
+
use crate::{colors::*, tokens::*};
|
|
6
|
+
|
|
7
|
+
pub struct Skeleton {
|
|
8
|
+
pub width: f32,
|
|
9
|
+
pub height: f32,
|
|
10
|
+
pub variant: SkeletonVariant,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#[derive(Clone, Copy)]
|
|
14
|
+
pub enum SkeletonVariant {
|
|
15
|
+
Text,
|
|
16
|
+
Title,
|
|
17
|
+
Avatar,
|
|
18
|
+
Block,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Skeleton {
|
|
22
|
+
pub fn text() -> Self {
|
|
23
|
+
Self { width: 100.0, height: 14.0, variant: SkeletonVariant::Text }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn title() -> Self {
|
|
27
|
+
Self { width: 150.0, height: 20.0, variant: SkeletonVariant::Title }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub fn avatar() -> Self {
|
|
31
|
+
Self { width: 40.0, height: 40.0, variant: SkeletonVariant::Avatar }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub fn block(width: f32, height: f32) -> Self {
|
|
35
|
+
Self { width, height, variant: SkeletonVariant::Block }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fn width(mut self, width: f32) -> Self {
|
|
39
|
+
self.width = width;
|
|
40
|
+
self
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub fn height(mut self, height: f32) -> Self {
|
|
44
|
+
self.height = height;
|
|
45
|
+
self
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub fn show(&self, ui: &mut Ui) -> Response {
|
|
49
|
+
let colors = Colors::default();
|
|
50
|
+
|
|
51
|
+
let rounding = match self.variant {
|
|
52
|
+
SkeletonVariant::Avatar => Rounding::same(self.width / 2.0),
|
|
53
|
+
SkeletonVariant::Block => Rounding::same(tokens::RADIUS),
|
|
54
|
+
_ => Rounding::same(tokens::RADIUS_SM),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
let (rect, response) = ui.allocate_at_least(vec2(self.width, self.height), Sense::hover());
|
|
58
|
+
ui.painter().rect_filled(rect, rounding, colors.surface_3);
|
|
59
|
+
ui.painter().rect_stroke(rect, rounding, Stroke::new(1.0, colors.border));
|
|
60
|
+
|
|
61
|
+
response
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//! Spinner component
|
|
2
|
+
|
|
3
|
+
use egui::{Color32, Ui};
|
|
4
|
+
|
|
5
|
+
use crate::colors::ACCENT;
|
|
6
|
+
|
|
7
|
+
pub fn spinner(ui: &mut Ui, size: f32) {
|
|
8
|
+
ui.add(egui::Spinner::new().size(size).color(ACCENT));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pub fn spinner_sm(ui: &mut Ui) {
|
|
12
|
+
spinner(ui, 16.0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub fn spinner_md(ui: &mut Ui) {
|
|
16
|
+
spinner(ui, 24.0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub fn spinner_lg(ui: &mut Ui) {
|
|
20
|
+
spinner(ui, 40.0);
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//! Table component
|
|
2
|
+
|
|
3
|
+
use egui::*;
|
|
4
|
+
use crate::{colors::*, tokens::*};
|
|
5
|
+
|
|
6
|
+
pub struct Table<'a> {
|
|
7
|
+
headers: Vec<&'a str>,
|
|
8
|
+
rows: Vec<Vec<String>>,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
impl<'a> Table<'a> {
|
|
12
|
+
pub fn new(headers: impl IntoIterator<Item = &'a str>) -> Self {
|
|
13
|
+
Self { headers: headers.into_iter().collect(), rows: Vec::new() }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn row(mut self, cells: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
|
17
|
+
self.rows.push(cells.into_iter().map(|s| s.into()).collect());
|
|
18
|
+
self
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub fn show(&self, ui: &mut Ui) {
|
|
22
|
+
let colors = Colors::default();
|
|
23
|
+
|
|
24
|
+
// Render headers
|
|
25
|
+
ui.horizontal(|ui| {
|
|
26
|
+
for header in &self.headers {
|
|
27
|
+
egui::Frame::none()
|
|
28
|
+
.fill(colors.surface_2)
|
|
29
|
+
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
30
|
+
.rounding(Rounding::same(tokens::RADIUS_SM))
|
|
31
|
+
.inner_margin(vec2(tokens::SPACE_2, tokens::SPACE_1))
|
|
32
|
+
.show(ui, |ui| {
|
|
33
|
+
ui.strong(*header);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
ui.add_space(tokens::SPACE_2);
|
|
39
|
+
|
|
40
|
+
// Render rows
|
|
41
|
+
for row in &self.rows {
|
|
42
|
+
ui.horizontal(|ui| {
|
|
43
|
+
for cell in row {
|
|
44
|
+
egui::Frame::none()
|
|
45
|
+
.fill(colors.surface)
|
|
46
|
+
.stroke(egui::Stroke::new(1.0, colors.border))
|
|
47
|
+
.rounding(Rounding::same(tokens::RADIUS_SM))
|
|
48
|
+
.inner_margin(vec2(tokens::SPACE_2, tokens::SPACE_1))
|
|
49
|
+
.show(ui, |ui| {
|
|
50
|
+
ui.label(cell);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//! Tabs component
|
|
2
|
+
|
|
3
|
+
use egui::Ui;
|
|
4
|
+
|
|
5
|
+
pub struct Tabs {
|
|
6
|
+
pub active: usize,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl Tabs {
|
|
10
|
+
pub fn new() -> Self {
|
|
11
|
+
Self { active: 0 }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn set_active(&mut self, index: usize) {
|
|
15
|
+
self.active = index;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn active(&self) -> usize {
|
|
19
|
+
self.active
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub fn show(&mut self, ui: &mut Ui, labels: &[&str]) {
|
|
23
|
+
ui.horizontal(|ui| {
|
|
24
|
+
for (i, label) in labels.iter().enumerate() {
|
|
25
|
+
let is_active = i == self.active;
|
|
26
|
+
let text = if is_active {
|
|
27
|
+
format!("▶ {}", label)
|
|
28
|
+
} else {
|
|
29
|
+
label.to_string()
|
|
30
|
+
};
|
|
31
|
+
if ui.button(&text).clicked() {
|
|
32
|
+
self.active = i;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl Default for Tabs {
|
|
40
|
+
fn default() -> Self {
|
|
41
|
+
Self::new()
|
|
42
|
+
}
|
|
43
|
+
}
|