cronixui 1.1.1 → 1.1.3

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.
Files changed (76) hide show
  1. package/README.md +12 -8
  2. package/package.json +71 -71
  3. package/packages/flutter/.qwen/settings.json +7 -0
  4. package/packages/flutter/pubspec.yaml +20 -20
  5. package/packages/go/cronixui/cronixui.go +926 -926
  6. package/packages/python/README.md +142 -0
  7. package/packages/python/cronixui/__init__.py +15 -6
  8. package/packages/python/cronixui/__pycache__/__init__.cpython-314.pyc +0 -0
  9. package/packages/python/cronixui/__pycache__/accordion.cpython-314.pyc +0 -0
  10. package/packages/python/cronixui/__pycache__/alert.cpython-314.pyc +0 -0
  11. package/packages/python/cronixui/__pycache__/avatar.cpython-314.pyc +0 -0
  12. package/packages/python/cronixui/__pycache__/badge.cpython-314.pyc +0 -0
  13. package/packages/python/cronixui/__pycache__/button.cpython-314.pyc +0 -0
  14. package/packages/python/cronixui/__pycache__/card.cpython-314.pyc +0 -0
  15. package/packages/python/cronixui/__pycache__/command_palette.cpython-314.pyc +0 -0
  16. package/packages/python/cronixui/__pycache__/core.cpython-314.pyc +0 -0
  17. package/packages/python/cronixui/__pycache__/dropdown.cpython-314.pyc +0 -0
  18. package/packages/python/cronixui/__pycache__/form.cpython-314.pyc +0 -0
  19. package/packages/python/cronixui/__pycache__/layout.cpython-314.pyc +0 -0
  20. package/packages/python/cronixui/__pycache__/list.cpython-314.pyc +0 -0
  21. package/packages/python/cronixui/__pycache__/loading.cpython-314.pyc +0 -0
  22. package/packages/python/cronixui/__pycache__/modal.cpython-314.pyc +0 -0
  23. package/packages/python/cronixui/__pycache__/nav.cpython-314.pyc +0 -0
  24. package/packages/python/cronixui/__pycache__/pagination.cpython-314.pyc +0 -0
  25. package/packages/python/cronixui/__pycache__/progress.cpython-314.pyc +0 -0
  26. package/packages/python/cronixui/__pycache__/search.cpython-314.pyc +0 -0
  27. package/packages/python/cronixui/__pycache__/table.cpython-314.pyc +0 -0
  28. package/packages/python/cronixui/__pycache__/tabs.cpython-314.pyc +0 -0
  29. package/packages/python/cronixui/__pycache__/toast.cpython-314.pyc +0 -0
  30. package/packages/python/cronixui/__pycache__/toggle.cpython-314.pyc +0 -0
  31. package/packages/python/cronixui/__pycache__/tokens.cpython-314.pyc +0 -0
  32. package/packages/python/cronixui/__pycache__/tooltip.cpython-314.pyc +0 -0
  33. package/packages/python/cronixui/alert.py +119 -36
  34. package/packages/python/cronixui/avatar.py +129 -22
  35. package/packages/python/cronixui/badge.py +161 -24
  36. package/packages/python/cronixui/button.py +96 -27
  37. package/packages/python/cronixui/card.py +206 -33
  38. package/packages/python/cronixui/core.py +212 -23
  39. package/packages/python/cronixui/form.py +552 -141
  40. package/packages/python/cronixui/layout.py +358 -96
  41. package/packages/python/cronixui/list.py +140 -37
  42. package/packages/python/cronixui/loading.py +107 -17
  43. package/packages/python/cronixui/progress.py +189 -47
  44. package/packages/python/cronixui/table.py +118 -31
  45. package/packages/python/cronixui/tooltip.py +117 -15
  46. package/packages/react/src/components/Accordion.tsx +82 -82
  47. package/packages/react/src/components/Button.tsx +47 -47
  48. package/packages/react/src/components/Card.tsx +69 -69
  49. package/packages/react/src/components/CommandPalette.tsx +131 -131
  50. package/packages/react/src/components/Dropdown.tsx +88 -88
  51. package/packages/react/src/components/FileInput.tsx +86 -86
  52. package/packages/react/src/components/FormGroup.tsx +36 -36
  53. package/packages/react/src/components/List.tsx +55 -55
  54. package/packages/react/src/components/Pagination.tsx +107 -107
  55. package/packages/react/src/components/Progress.tsx +49 -49
  56. package/packages/react/src/components/Search.tsx +95 -95
  57. package/packages/react/src/components/Sidebar.tsx +64 -64
  58. package/packages/react/src/components/Stack.tsx +69 -69
  59. package/packages/react/src/components/Table.tsx +90 -90
  60. package/packages/react/src/components/Toast.tsx +134 -134
  61. package/packages/react/src/components/Typography.tsx +66 -66
  62. package/packages/react/src/index.ts +40 -40
  63. package/packages/react/src/styles.css +2039 -2039
  64. package/packages/rust/cronixui/src/components/avatar.rs +85 -85
  65. package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -58
  66. package/packages/rust/cronixui/src/components/card.rs +259 -259
  67. package/packages/rust/cronixui/src/components/command_palette.rs +254 -254
  68. package/packages/rust/cronixui/src/components/dropdown.rs +179 -179
  69. package/packages/rust/cronixui/src/components/file_input.rs +74 -74
  70. package/packages/rust/cronixui/src/components/mod.rs +51 -51
  71. package/packages/rust/cronixui/src/components/search.rs +185 -185
  72. package/packages/rust/cronixui/src/components/skeleton.rs +63 -63
  73. package/packages/rust/cronixui/src/components/table.rs +56 -56
  74. package/packages/rust/cronixui/src/lib.rs +128 -128
  75. package/packages/web/dist/cronixui.css +97 -93
  76. package/packages/web/dist/cronixui.min.css +1 -1
@@ -1,185 +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
- }
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
+ }
@@ -1,63 +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
- }
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
+ }
@@ -1,56 +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
- }
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
+ }