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.
Files changed (169) hide show
  1. package/README.md +20 -5
  2. package/package.json +20 -5
  3. package/packages/flutter/lib/cronixui.dart +41 -0
  4. package/packages/flutter/lib/src/tokens/colors.dart +34 -0
  5. package/packages/flutter/lib/src/tokens/spacing.dart +54 -0
  6. package/packages/flutter/lib/src/tokens/theme.dart +174 -0
  7. package/packages/flutter/lib/src/widgets/cn_accordion.dart +254 -0
  8. package/packages/flutter/lib/src/widgets/cn_alert.dart +137 -0
  9. package/packages/flutter/lib/src/widgets/cn_avatar.dart +98 -0
  10. package/packages/flutter/lib/src/widgets/cn_badge.dart +80 -0
  11. package/packages/flutter/lib/src/widgets/cn_breadcrumb.dart +88 -0
  12. package/packages/flutter/lib/src/widgets/cn_button.dart +137 -0
  13. package/packages/flutter/lib/src/widgets/cn_card.dart +99 -0
  14. package/packages/flutter/lib/src/widgets/cn_checkbox.dart +77 -0
  15. package/packages/flutter/lib/src/widgets/cn_command_palette.dart +299 -0
  16. package/packages/flutter/lib/src/widgets/cn_container.dart +131 -0
  17. package/packages/flutter/lib/src/widgets/cn_dropdown.dart +149 -0
  18. package/packages/flutter/lib/src/widgets/cn_file_input.dart +113 -0
  19. package/packages/flutter/lib/src/widgets/cn_footer.dart +108 -0
  20. package/packages/flutter/lib/src/widgets/cn_header.dart +173 -0
  21. package/packages/flutter/lib/src/widgets/cn_input.dart +142 -0
  22. package/packages/flutter/lib/src/widgets/cn_list.dart +150 -0
  23. package/packages/flutter/lib/src/widgets/cn_modal.dart +213 -0
  24. package/packages/flutter/lib/src/widgets/cn_nav.dart +157 -0
  25. package/packages/flutter/lib/src/widgets/cn_pagination.dart +193 -0
  26. package/packages/flutter/lib/src/widgets/cn_progress.dart +146 -0
  27. package/packages/flutter/lib/src/widgets/cn_radio.dart +133 -0
  28. package/packages/flutter/lib/src/widgets/cn_search.dart +183 -0
  29. package/packages/flutter/lib/src/widgets/cn_select.dart +244 -0
  30. package/packages/flutter/lib/src/widgets/cn_sidebar.dart +207 -0
  31. package/packages/flutter/lib/src/widgets/cn_skeleton.dart +136 -0
  32. package/packages/flutter/lib/src/widgets/cn_slider.dart +141 -0
  33. package/packages/flutter/lib/src/widgets/cn_spinner.dart +85 -0
  34. package/packages/flutter/lib/src/widgets/cn_stat.dart +135 -0
  35. package/packages/flutter/lib/src/widgets/cn_table.dart +136 -0
  36. package/packages/flutter/lib/src/widgets/cn_tabs.dart +229 -0
  37. package/packages/flutter/lib/src/widgets/cn_tag.dart +185 -0
  38. package/packages/flutter/lib/src/widgets/cn_textarea.dart +143 -0
  39. package/packages/flutter/lib/src/widgets/cn_toast.dart +121 -0
  40. package/packages/flutter/lib/src/widgets/cn_toggle.dart +78 -0
  41. package/packages/flutter/lib/src/widgets/cn_tooltip.dart +118 -0
  42. package/packages/flutter/pubspec.yaml +20 -0
  43. package/packages/go/cronixui/cronixui.go +784 -237
  44. package/packages/go/cronixui/go.mod +32 -0
  45. package/packages/go/cronixui/go.sum +666 -0
  46. package/packages/python/cronixui/__init__.py +59 -3
  47. package/packages/python/cronixui/alert.py +61 -0
  48. package/packages/python/cronixui/avatar.py +50 -0
  49. package/packages/python/cronixui/badge.py +46 -0
  50. package/packages/python/cronixui/button.py +64 -0
  51. package/packages/python/cronixui/card.py +62 -0
  52. package/packages/python/cronixui/form.py +255 -0
  53. package/packages/python/cronixui/layout.py +143 -0
  54. package/packages/python/cronixui/list.py +51 -0
  55. package/packages/python/cronixui/loading.py +36 -0
  56. package/packages/python/cronixui/progress.py +90 -0
  57. package/packages/python/cronixui/table.py +48 -0
  58. package/packages/python/cronixui/tooltip.py +28 -0
  59. package/packages/react/src/components/Accordion.tsx +82 -0
  60. package/packages/react/src/components/Alert.tsx +80 -0
  61. package/packages/react/src/components/Avatar.tsx +54 -0
  62. package/packages/react/src/components/Badge.tsx +32 -0
  63. package/packages/react/src/components/Breadcrumb.tsx +50 -0
  64. package/packages/react/src/components/Button.tsx +47 -0
  65. package/packages/react/src/components/Card.tsx +69 -0
  66. package/packages/react/src/components/Checkbox.tsx +30 -0
  67. package/packages/react/src/components/CommandPalette.tsx +131 -0
  68. package/packages/react/src/components/Container.tsx +26 -0
  69. package/packages/react/src/components/Dropdown.tsx +88 -0
  70. package/packages/react/src/components/FileInput.tsx +86 -0
  71. package/packages/react/src/components/Footer.tsx +36 -0
  72. package/packages/react/src/components/FormGroup.tsx +36 -0
  73. package/packages/react/src/components/Header.tsx +29 -0
  74. package/packages/react/src/components/Input.tsx +54 -0
  75. package/packages/react/src/components/List.tsx +55 -0
  76. package/packages/react/src/components/Modal.tsx +89 -0
  77. package/packages/react/src/components/Nav.tsx +63 -0
  78. package/packages/react/src/components/Pagination.tsx +107 -0
  79. package/packages/react/src/components/Progress.tsx +49 -0
  80. package/packages/react/src/components/Radio.tsx +64 -0
  81. package/packages/react/src/components/Search.tsx +95 -0
  82. package/packages/react/src/components/Select.tsx +41 -0
  83. package/packages/react/src/components/Sidebar.tsx +64 -0
  84. package/packages/react/src/components/Skeleton.tsx +39 -0
  85. package/packages/react/src/components/Slider.tsx +32 -0
  86. package/packages/react/src/components/Spinner.tsx +24 -0
  87. package/packages/react/src/components/Stack.tsx +69 -0
  88. package/packages/react/src/components/Stat.tsx +35 -0
  89. package/packages/react/src/components/Table.tsx +90 -0
  90. package/packages/react/src/components/Tabs.tsx +85 -0
  91. package/packages/react/src/components/Tag.tsx +30 -0
  92. package/packages/react/src/components/Textarea.tsx +21 -0
  93. package/packages/react/src/components/Toast.tsx +134 -0
  94. package/packages/react/src/components/Toggle.tsx +58 -0
  95. package/packages/react/src/components/Tooltip.tsx +31 -0
  96. package/packages/react/src/components/Typography.tsx +66 -0
  97. package/packages/react/src/index.ts +40 -0
  98. package/packages/react/src/styles.css +2039 -0
  99. package/packages/react/src/tokens/index.ts +94 -0
  100. package/packages/rust/cronixui/src/colors.rs +135 -0
  101. package/packages/rust/cronixui/src/components/accordion.rs +47 -0
  102. package/packages/rust/cronixui/src/components/alert.rs +95 -0
  103. package/packages/rust/cronixui/src/components/avatar.rs +85 -0
  104. package/packages/rust/cronixui/src/components/badge.rs +35 -0
  105. package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -0
  106. package/packages/rust/cronixui/src/components/button.rs +70 -0
  107. package/packages/rust/cronixui/src/components/card.rs +259 -0
  108. package/packages/rust/cronixui/src/components/command_palette.rs +254 -0
  109. package/packages/rust/cronixui/src/components/dropdown.rs +179 -0
  110. package/packages/rust/cronixui/src/components/file_input.rs +74 -0
  111. package/packages/rust/cronixui/src/components/input.rs +21 -0
  112. package/packages/rust/cronixui/src/components/list.rs +38 -0
  113. package/packages/rust/cronixui/src/components/mod.rs +51 -0
  114. package/packages/rust/cronixui/src/{modal.rs → components/modal.rs} +15 -1
  115. package/packages/rust/cronixui/src/components/nav.rs +19 -0
  116. package/packages/rust/cronixui/src/{pagination.rs → components/pagination.rs} +14 -13
  117. package/packages/rust/cronixui/src/components/progress.rs +50 -0
  118. package/packages/rust/cronixui/src/components/search.rs +185 -0
  119. package/packages/rust/cronixui/src/components/skeleton.rs +63 -0
  120. package/packages/rust/cronixui/src/components/spinner.rs +21 -0
  121. package/packages/rust/cronixui/src/components/table.rs +56 -0
  122. package/packages/rust/cronixui/src/components/tabs.rs +43 -0
  123. package/packages/rust/cronixui/src/components/toast.rs +69 -0
  124. package/packages/rust/cronixui/src/{toggle.rs → components/toggle.rs} +7 -5
  125. package/packages/rust/cronixui/src/components/tooltip.rs +11 -0
  126. package/packages/rust/cronixui/src/lib.rs +111 -64
  127. package/packages/rust/cronixui/src/tokens.rs +97 -127
  128. package/packages/web/src/variables.css +81 -81
  129. package/packages/go/cronixui/tokens.go +0 -129
  130. package/packages/python/cronixui/pyproject.toml +0 -11
  131. package/packages/react/src/components/Accordion.jsx +0 -50
  132. package/packages/react/src/components/Alert.jsx +0 -62
  133. package/packages/react/src/components/Avatar.jsx +0 -34
  134. package/packages/react/src/components/Badge.jsx +0 -15
  135. package/packages/react/src/components/Breadcrumb.jsx +0 -27
  136. package/packages/react/src/components/Button.jsx +0 -21
  137. package/packages/react/src/components/Card.jsx +0 -23
  138. package/packages/react/src/components/Checkbox.jsx +0 -27
  139. package/packages/react/src/components/CommandPalette.jsx +0 -93
  140. package/packages/react/src/components/Dropdown.jsx +0 -48
  141. package/packages/react/src/components/FileInput.jsx +0 -44
  142. package/packages/react/src/components/Input.jsx +0 -22
  143. package/packages/react/src/components/List.jsx +0 -29
  144. package/packages/react/src/components/Modal.jsx +0 -65
  145. package/packages/react/src/components/Nav.jsx +0 -50
  146. package/packages/react/src/components/Pagination.jsx +0 -81
  147. package/packages/react/src/components/Progress.jsx +0 -23
  148. package/packages/react/src/components/Radio.jsx +0 -50
  149. package/packages/react/src/components/Search.jsx +0 -70
  150. package/packages/react/src/components/Select.jsx +0 -33
  151. package/packages/react/src/components/Skeleton.jsx +0 -15
  152. package/packages/react/src/components/Slider.jsx +0 -29
  153. package/packages/react/src/components/Spinner.jsx +0 -5
  154. package/packages/react/src/components/Stat.jsx +0 -19
  155. package/packages/react/src/components/Table.jsx +0 -48
  156. package/packages/react/src/components/Tabs.jsx +0 -65
  157. package/packages/react/src/components/Tag.jsx +0 -19
  158. package/packages/react/src/components/Textarea.jsx +0 -17
  159. package/packages/react/src/components/Toast.jsx +0 -78
  160. package/packages/react/src/components/Toggle.jsx +0 -34
  161. package/packages/react/src/components/Tooltip.jsx +0 -12
  162. package/packages/react/src/index.d.ts +0 -103
  163. package/packages/react/src/index.js +0 -33
  164. package/packages/rust/cronixui/src/accordion.rs +0 -49
  165. package/packages/rust/cronixui/src/command_palette.rs +0 -62
  166. package/packages/rust/cronixui/src/dropdown.rs +0 -31
  167. package/packages/rust/cronixui/src/search.rs +0 -49
  168. package/packages/rust/cronixui/src/tabs.rs +0 -23
  169. package/packages/rust/cronixui/src/toast.rs +0 -70
@@ -1,379 +1,926 @@
1
1
  package cronixui
2
2
 
3
- import "fmt"
3
+ import (
4
+ "image/color"
5
+ "strings"
6
+
7
+ "fyne.io/fyne/v2"
8
+ "fyne.io/fyne/v2/app"
9
+ "fyne.io/fyne/v2/canvas"
10
+ "fyne.io/fyne/v2/container"
11
+ "fyne.io/fyne/v2/layout"
12
+ "fyne.io/fyne/v2/theme"
13
+ "fyne.io/fyne/v2/widget"
14
+ )
4
15
 
5
- // Version of CronixUI
6
- const Version = "1.0.4"
16
+ const Version = "1.0.6"
17
+
18
+ // =============================================================================
19
+ // DESIGN TOKENS
20
+ // =============================================================================
21
+
22
+ type Colors struct {
23
+ BG color.Color
24
+ Surface color.Color
25
+ Surface2 color.Color
26
+ Surface3 color.Color
27
+ Surface4 color.Color
28
+ Text color.Color
29
+ TextMuted color.Color
30
+ TextDim color.Color
31
+ Accent color.Color
32
+ AccentHover color.Color
33
+ AccentLight color.Color
34
+ AccentGlow color.Color
35
+ AccentText color.Color
36
+ Success color.Color
37
+ SuccessBorder color.Color
38
+ SuccessText color.Color
39
+ Warning color.Color
40
+ WarningBorder color.Color
41
+ WarningText color.Color
42
+ Error color.Color
43
+ ErrorBorder color.Color
44
+ ErrorText color.Color
45
+ Info color.Color
46
+ InfoBorder color.Color
47
+ InfoText color.Color
48
+ Border color.Color
49
+ BorderHover color.Color
50
+ BorderFocus color.Color
51
+ }
52
+
53
+ func DefaultColors() *Colors {
54
+ return &Colors{
55
+ BG: color.RGBA{R: 10, G: 10, B: 10, A: 255},
56
+ Surface: color.RGBA{R: 17, G: 17, B: 17, A: 255},
57
+ Surface2: color.RGBA{R: 26, G: 26, B: 26, A: 255},
58
+ Surface3: color.RGBA{R: 34, G: 34, B: 34, A: 255},
59
+ Surface4: color.RGBA{R: 42, G: 42, B: 42, A: 255},
60
+ Text: color.RGBA{R: 240, G: 237, B: 232, A: 255},
61
+ TextMuted: color.RGBA{R: 240, G: 237, B: 232, A: 128},
62
+ TextDim: color.RGBA{R: 240, G: 237, B: 232, A: 64},
63
+ Accent: color.RGBA{R: 107, G: 35, B: 35, A: 255},
64
+ AccentHover: color.RGBA{R: 125, G: 42, B: 42, A: 255},
65
+ AccentLight: color.RGBA{R: 138, G: 53, B: 53, A: 255},
66
+ AccentGlow: color.RGBA{R: 107, G: 35, B: 35, A: 77},
67
+ AccentText: color.RGBA{R: 201, G: 122, B: 122, A: 255},
68
+ Success: color.RGBA{R: 30, G: 80, B: 40, A: 255},
69
+ SuccessBorder: color.RGBA{R: 60, G: 140, B: 70, A: 102},
70
+ SuccessText: color.RGBA{R: 107, G: 196, B: 122, A: 255},
71
+ Warning: color.RGBA{R: 80, G: 60, B: 20, A: 255},
72
+ WarningBorder: color.RGBA{R: 150, G: 110, B: 30, A: 102},
73
+ WarningText: color.RGBA{R: 196, G: 164, B: 58, A: 255},
74
+ Error: color.RGBA{R: 80, G: 20, B: 20, A: 255},
75
+ ErrorBorder: color.RGBA{R: 180, G: 60, B: 60, A: 102},
76
+ ErrorText: color.RGBA{R: 196, G: 107, B: 107, A: 255},
77
+ Info: color.RGBA{R: 20, G: 53, B: 80, A: 255},
78
+ InfoBorder: color.RGBA{R: 60, G: 140, B: 200, A: 102},
79
+ InfoText: color.RGBA{R: 107, G: 168, B: 196, A: 255},
80
+ Border: color.RGBA{R: 255, G: 255, B: 255, A: 20},
81
+ BorderHover: color.RGBA{R: 255, G: 255, B: 255, A: 38},
82
+ BorderFocus: color.RGBA{R: 255, G: 255, B: 255, A: 64},
83
+ }
84
+ }
7
85
 
8
- // ToastType represents toast notification type
9
- type ToastType int
86
+ type Typography struct {
87
+ FontFamily string
88
+ FontMono string
89
+ FontSizeXS float32
90
+ FontSizeSM float32
91
+ FontSizeBase float32
92
+ FontSizeMD float32
93
+ FontSizeLG float32
94
+ FontSizeXL float32
95
+ FontSize2XL float32
96
+ FontSize3XL float32
97
+ }
98
+
99
+ func DefaultTypography() *Typography {
100
+ return &Typography{
101
+ FontFamily: "Outfit",
102
+ FontMono: "JetBrains Mono",
103
+ FontSizeXS: 11,
104
+ FontSizeSM: 12,
105
+ FontSizeBase: 13,
106
+ FontSizeMD: 14,
107
+ FontSizeLG: 16,
108
+ FontSizeXL: 20,
109
+ FontSize2XL: 28,
110
+ FontSize3XL: 36,
111
+ }
112
+ }
10
113
 
11
- const (
12
- ToastTypeSuccess ToastType = iota
13
- ToastTypeError
14
- ToastTypeWarning
15
- ToastTypeInfo
16
- )
114
+ type Spacing struct {
115
+ Space1, Space2, Space3, Space4, Space5, Space6, Space8, Space10, Space12 float32
116
+ }
17
117
 
18
- // Toast represents a toast notification
19
- type Toast struct {
20
- Title string
21
- Message string
22
- Type ToastType
23
- Duration int
118
+ func DefaultSpacing() *Spacing {
119
+ return &Spacing{4, 8, 12, 16, 20, 24, 32, 40, 48}
24
120
  }
25
121
 
26
- // ToastShow displays a toast notification
27
- func ToastShow(message string, opts ...func(*Toast)) *Toast {
28
- t := &Toast{Message: message, Type: ToastTypeInfo, Duration: 4000}
29
- for _, opt := range opts {
30
- opt(t)
31
- }
32
- return t
122
+ type RadiusTokens struct {
123
+ SM, Default, LG, XL, Full float32
33
124
  }
34
125
 
35
- // WithTitle sets toast title
36
- func WithTitle(title string) func(*Toast) {
37
- return func(t *Toast) { t.Title = title }
126
+ func DefaultRadiusTokens() *RadiusTokens {
127
+ return &RadiusTokens{6, 10, 14, 20, 9999}
38
128
  }
39
129
 
40
- // WithType sets toast type
41
- func WithType(toastType ToastType) func(*Toast) {
42
- return func(t *Toast) { t.Type = toastType }
130
+ type Shadow struct {
131
+ SM, Default, LG, Glow string
43
132
  }
44
133
 
45
- // WithDuration sets toast duration
46
- func WithDuration(duration int) func(*Toast) {
47
- return func(t *Toast) { t.Duration = duration }
134
+ func DefaultShadow() *Shadow {
135
+ return &Shadow{
136
+ SM: "0 1px 2px rgba(0, 0, 0, 0.3)",
137
+ Default: "0 4px 12px rgba(0, 0, 0, 0.4)",
138
+ LG: "0 8px 24px rgba(0, 0, 0, 0.5)",
139
+ Glow: "0 0 20px rgba(107, 35, 35, 0.3)",
140
+ }
48
141
  }
49
142
 
50
- // ToastSuccess shows a success toast
51
- func ToastSuccess(message string) *Toast {
52
- return ToastShow(message, WithType(ToastTypeSuccess))
143
+ type Transition struct {
144
+ Fast, Default, Slow string
53
145
  }
54
146
 
55
- // ToastError shows an error toast
56
- func ToastError(message string) *Toast {
57
- return ToastShow(message, WithType(ToastTypeError))
147
+ func DefaultTransition() *Transition {
148
+ return &Transition{"0.1s ease", "0.15s ease", "0.25s ease"}
58
149
  }
59
150
 
60
- // ToastWarning shows a warning toast
61
- func ToastWarning(message string) *Toast {
62
- return ToastShow(message, WithType(ToastTypeWarning))
151
+ type ZIndexTokens struct {
152
+ Dropdown, Sticky, Fixed, ModalBackdrop, Modal, Popover, Tooltip, Toast int
63
153
  }
64
154
 
65
- // ToastInfo shows an info toast
66
- func ToastInfo(message string) *Toast {
67
- return ToastShow(message, WithType(ToastTypeInfo))
155
+ func DefaultZIndexTokens() *ZIndexTokens {
156
+ return &ZIndexTokens{100, 200, 300, 400, 500, 600, 700, 800}
68
157
  }
69
158
 
70
- // Toggle represents a toggle switch
71
- type Toggle struct {
72
- on bool
159
+ type LayoutTokens struct {
160
+ ContainerMax, SidebarWidth, HeaderHeight float32
73
161
  }
74
162
 
75
- // NewToggle creates a new toggle
76
- func NewToggle() *Toggle {
77
- return &Toggle{}
163
+ func DefaultLayoutTokens() *LayoutTokens {
164
+ return &LayoutTokens{1200, 260, 56}
78
165
  }
79
166
 
80
- // Toggle flips the toggle state
81
- func (t *Toggle) Toggle() {
82
- t.on = !t.on
167
+ // =============================================================================
168
+ // THEME
169
+ // =============================================================================
170
+
171
+ type Theme struct {
172
+ colors *Colors
173
+ spacing *Spacing
174
+ radius *RadiusTokens
83
175
  }
84
176
 
85
- // IsOn returns current toggle state
86
- func (t *Toggle) IsOn() bool {
87
- return t.on
177
+ func NewTheme() fyne.Theme {
178
+ return &Theme{
179
+ colors: DefaultColors(),
180
+ spacing: DefaultSpacing(),
181
+ radius: DefaultRadiusTokens(),
182
+ }
88
183
  }
89
184
 
90
- // SetOn sets the toggle state
91
- func (t *Toggle) SetOn(value bool) {
92
- t.on = value
185
+ func (t *Theme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
186
+ switch name {
187
+ case theme.ColorNameBackground:
188
+ return t.colors.BG
189
+ case theme.ColorNameButton:
190
+ return t.colors.Surface2
191
+ case theme.ColorNameDisabledButton:
192
+ return t.colors.Surface3
193
+ case theme.ColorNameInputBackground:
194
+ return t.colors.Surface
195
+ case theme.ColorNameInputBorder:
196
+ return t.colors.Border
197
+ case theme.ColorNameDisabled:
198
+ return t.colors.TextDim
199
+ case theme.ColorNameForeground:
200
+ return t.colors.Text
201
+ case theme.ColorNamePlaceHolder:
202
+ return t.colors.TextMuted
203
+ case theme.ColorNamePrimary:
204
+ return t.colors.Accent
205
+ case theme.ColorNameHover:
206
+ return t.colors.Surface3
207
+ case theme.ColorNamePressed:
208
+ return t.colors.Surface4
209
+ case theme.ColorNameScrollBar:
210
+ return t.colors.Surface3
211
+ case theme.ColorNameShadow:
212
+ return color.RGBA{R: 0, G: 0, B: 0, A: 50}
213
+ case theme.ColorNameSuccess:
214
+ return t.colors.Success
215
+ case theme.ColorNameWarning:
216
+ return t.colors.Warning
217
+ case theme.ColorNameError:
218
+ return t.colors.Error
219
+ default:
220
+ return t.colors.Text
221
+ }
93
222
  }
94
223
 
95
- // Modal represents a modal dialog
96
- type Modal struct {
97
- open bool
224
+ func (t *Theme) Font(style fyne.TextStyle) fyne.Resource {
225
+ return theme.DefaultTheme().Font(style)
226
+ }
227
+
228
+ func (t *Theme) Icon(name fyne.ThemeIconName) fyne.Resource {
229
+ return theme.DefaultTheme().Icon(name)
230
+ }
231
+
232
+ func (t *Theme) Size(name fyne.ThemeSizeName) float32 {
233
+ switch name {
234
+ case theme.SizeNamePadding:
235
+ return t.spacing.Space2
236
+ case theme.SizeNameInlinePadding:
237
+ return t.spacing.Space3
238
+ case theme.SizeNameScrollBar:
239
+ return 8
240
+ case theme.SizeNameScrollBarSmall:
241
+ return 4
242
+ case theme.SizeNameSeparatorThickness:
243
+ return 1
244
+ case theme.SizeNameText:
245
+ return t.spacing.Space4
246
+ case theme.SizeNameHeadingText:
247
+ return t.spacing.Space6
248
+ case theme.SizeNameSubHeadingText:
249
+ return t.spacing.Space5
250
+ case theme.SizeNameCaptionText:
251
+ return t.spacing.Space2
252
+ case theme.SizeNameInputBorder:
253
+ return 1
254
+ case theme.SizeNameInputRadius:
255
+ return t.radius.Default
256
+ case theme.SizeNameSelectionRadius:
257
+ return t.radius.SM
258
+ default:
259
+ return 0
260
+ }
261
+ }
262
+
263
+ // =============================================================================
264
+ // BUTTONS
265
+ // =============================================================================
266
+
267
+ type ButtonVariant int
268
+
269
+ const (
270
+ ButtonDefault ButtonVariant = iota
271
+ ButtonPrimary
272
+ ButtonGhost
273
+ ButtonOutline
274
+ ButtonDanger
275
+ ButtonSuccess
276
+ )
277
+
278
+ func NewButton(text string, variant ButtonVariant, onTap func()) *widget.Button {
279
+ btn := widget.NewButton(text, onTap)
280
+ switch variant {
281
+ case ButtonPrimary:
282
+ btn.Importance = widget.HighImportance
283
+ case ButtonDanger:
284
+ btn.Importance = widget.DangerImportance
285
+ case ButtonSuccess:
286
+ btn.Importance = widget.HighImportance
287
+ case ButtonGhost, ButtonOutline:
288
+ btn.Importance = widget.LowImportance
289
+ default:
290
+ btn.Importance = widget.MediumImportance
291
+ }
292
+ return btn
98
293
  }
99
294
 
100
- // NewModal creates a new modal
101
- func NewModal() *Modal {
102
- return &Modal{}
295
+ func NewButtonGroup(buttons ...*widget.Button) *fyne.Container {
296
+ return container.NewHBox(buttons...)
103
297
  }
104
298
 
105
- // Open opens the modal
106
- func (m *Modal) Open() {
107
- m.open = true
299
+ // =============================================================================
300
+ // CARD
301
+ // =============================================================================
302
+
303
+ // CardOption is a function that configures a card.
304
+ type CardOption func(*CardConfig)
305
+
306
+ // CardConfig holds card configuration.
307
+ type CardConfig struct {
308
+ Title string
309
+ Subtitle string
310
+ Icon fyne.CanvasObject
311
+ Footer fyne.CanvasObject
312
+ Clickable bool
108
313
  }
109
314
 
110
- // Close closes the modal
111
- func (m *Modal) Close() {
112
- m.open = false
315
+ // WithCardTitle sets the card title.
316
+ func WithCardTitle(title string) CardOption {
317
+ return func(c *CardConfig) { c.Title = title }
113
318
  }
114
319
 
115
- // IsOpen returns whether modal is open
116
- func (m *Modal) IsOpen() bool {
117
- return m.open
320
+ // WithCardSubtitle sets the card subtitle.
321
+ func WithCardSubtitle(subtitle string) CardOption {
322
+ return func(c *CardConfig) { c.Subtitle = subtitle }
118
323
  }
119
324
 
120
- // Dropdown represents a dropdown menu
121
- type Dropdown struct {
122
- open bool
325
+ // WithCardIcon sets the card icon.
326
+ func WithCardIcon(icon fyne.CanvasObject) CardOption {
327
+ return func(c *CardConfig) { c.Icon = icon }
123
328
  }
124
329
 
125
- // NewDropdown creates a new dropdown
126
- func NewDropdown() *Dropdown {
127
- return &Dropdown{}
330
+ // WithCardFooter sets the card footer.
331
+ func WithCardFooter(footer fyne.CanvasObject) CardOption {
332
+ return func(c *CardConfig) { c.Footer = footer }
128
333
  }
129
334
 
130
- // Open opens the dropdown
131
- func (d *Dropdown) Open() {
132
- d.open = true
335
+ // WithCardClickable makes the card clickable.
336
+ func WithCardClickable(clickable bool) CardOption {
337
+ return func(c *CardConfig) { c.Clickable = clickable }
133
338
  }
134
339
 
135
- // Close closes the dropdown
136
- func (d *Dropdown) Close() {
137
- d.open = false
340
+ // NewCard creates a card widget with optional icon, title, subtitle, and footer.
341
+ func NewCard(content fyne.CanvasObject, options ...CardOption) *fyne.Container {
342
+ config := &CardConfig{}
343
+ for _, opt := range options {
344
+ opt(config)
345
+ }
346
+
347
+ c := DefaultColors()
348
+
349
+ var headerObjects []fyne.CanvasObject
350
+ if config.Icon != nil {
351
+ headerObjects = append(headerObjects, config.Icon)
352
+ }
353
+ if config.Title != "" {
354
+ titleText := canvas.NewText(config.Title, c.Text)
355
+ titleText.TextSize = 14
356
+ titleText.TextStyle = fyne.TextStyle{Bold: true}
357
+ headerObjects = append(headerObjects, titleText)
358
+ }
359
+ if config.Subtitle != "" {
360
+ subtitleText := canvas.NewText(config.Subtitle, c.TextMuted)
361
+ subtitleText.TextSize = 12
362
+ headerObjects = append(headerObjects, subtitleText)
363
+ }
364
+
365
+ var header *fyne.Container
366
+ if len(headerObjects) > 0 {
367
+ header = container.NewVBox(headerObjects...)
368
+ }
369
+
370
+ var body fyne.CanvasObject
371
+ if header != nil {
372
+ body = container.NewVBox(header, content)
373
+ } else {
374
+ body = content
375
+ }
376
+
377
+ var finalContent *fyne.Container
378
+ if config.Footer != nil {
379
+ finalContent = container.NewVBox(body, canvas.NewRectangle(c.Border), config.Footer)
380
+ } else {
381
+ if c, ok := body.(*fyne.Container); ok {
382
+ finalContent = c
383
+ } else {
384
+ finalContent = container.NewMax(body)
385
+ }
386
+ }
387
+
388
+ bg := canvas.NewRectangle(c.Surface)
389
+ bg.StrokeColor = c.Border
390
+ bg.StrokeWidth = 1
391
+ bg.CornerRadius = 14
392
+
393
+ return container.NewStack(bg, container.NewPadded(finalContent))
138
394
  }
139
395
 
140
- // Toggle flips dropdown state
141
- func (d *Dropdown) Toggle() {
142
- d.open = !d.open
396
+ // =============================================================================
397
+ // INPUTS
398
+ // =============================================================================
399
+
400
+ func NewInput(placeholder string) *widget.Entry {
401
+ entry := widget.NewEntry()
402
+ entry.SetPlaceHolder(placeholder)
403
+ return entry
143
404
  }
144
405
 
145
- // IsOpen returns whether dropdown is open
146
- func (d *Dropdown) IsOpen() bool {
147
- return d.open
406
+ func NewTextarea(placeholder string) *widget.Entry {
407
+ entry := widget.NewMultiLineEntry()
408
+ entry.SetPlaceHolder(placeholder)
409
+ return entry
148
410
  }
149
411
 
150
- // Tabs represents a tabs component
151
- type Tabs struct {
152
- activeIndex int
412
+ func NewPassword(placeholder string) *widget.Entry {
413
+ entry := widget.NewPasswordEntry()
414
+ entry.SetPlaceHolder(placeholder)
415
+ return entry
153
416
  }
154
417
 
155
- // NewTabs creates new tabs
156
- func NewTabs() *Tabs {
157
- return &Tabs{activeIndex: 0}
418
+ // =============================================================================
419
+ // FORM ELEMENTS
420
+ // =============================================================================
421
+
422
+ func NewCheckbox(label string, onChanged func(bool)) *widget.Check {
423
+ return widget.NewCheck(label, onChanged)
158
424
  }
159
425
 
160
- // SetActive sets active tab by index
161
- func (t *Tabs) SetActive(index int) {
162
- t.activeIndex = index
426
+ func NewRadio(options []string, onChanged func(string)) *widget.RadioGroup {
427
+ return widget.NewRadioGroup(options, onChanged)
163
428
  }
164
429
 
165
- // ActiveIndex returns current active index
166
- func (t *Tabs) ActiveIndex() int {
167
- return t.activeIndex
430
+ func NewSelect(options []string, onChanged func(string)) *widget.Select {
431
+ return widget.NewSelect(options, onChanged)
168
432
  }
169
433
 
170
- // Accordion represents an accordion component
171
- type Accordion struct {
172
- openIndices map[int]bool
434
+ func NewSlider(min, max float64) *widget.Slider {
435
+ return widget.NewSlider(min, max)
173
436
  }
174
437
 
175
- // NewAccordion creates a new accordion
176
- func NewAccordion() *Accordion {
177
- return &Accordion{openIndices: make(map[int]bool)}
438
+ // =============================================================================
439
+ // PROGRESS & LOADING
440
+ // =============================================================================
441
+
442
+ func NewProgress() *widget.ProgressBar {
443
+ return widget.NewProgressBar()
178
444
  }
179
445
 
180
- // Toggle toggles an accordion item
181
- func (a *Accordion) Toggle(index int) {
182
- a.openIndices[index] = !a.openIndices[index]
446
+ func NewProgressInfinite() *widget.ProgressBarInfinite {
447
+ return widget.NewProgressBarInfinite()
183
448
  }
184
449
 
185
- // Open opens an accordion item
186
- func (a *Accordion) Open(index int) {
187
- a.openIndices[index] = true
450
+ // =============================================================================
451
+ // TOGGLE
452
+ // =============================================================================
453
+
454
+ type Toggle struct {
455
+ widget.BaseWidget
456
+ on bool
457
+ onToggle func(bool)
188
458
  }
189
459
 
190
- // Close closes an accordion item
191
- func (a *Accordion) Close(index int) {
192
- a.openIndices[index] = false
460
+ func NewToggle(onToggle func(bool)) *Toggle {
461
+ t := &Toggle{onToggle: onToggle}
462
+ t.ExtendBaseWidget(t)
463
+ return t
193
464
  }
194
465
 
195
- // OpenAll opens all items up to total
196
- func (a *Accordion) OpenAll(total int) {
197
- for i := 0; i < total; i++ {
198
- a.openIndices[i] = true
466
+ func (t *Toggle) Toggle() {
467
+ t.on = !t.on
468
+ if t.onToggle != nil {
469
+ t.onToggle(t.on)
199
470
  }
471
+ t.Refresh()
200
472
  }
201
473
 
202
- // CloseAll closes all accordion items
203
- func (a *Accordion) CloseAll() {
204
- a.openIndices = make(map[int]bool)
474
+ func (t *Toggle) IsOn() bool { return t.on }
475
+ func (t *Toggle) SetOn(value bool) { t.on = value; t.Refresh() }
476
+
477
+ // =============================================================================
478
+ // MODAL
479
+ // =============================================================================
480
+
481
+ type Modal struct {
482
+ widget.BaseWidget
483
+ content fyne.CanvasObject
484
+ open bool
485
+ window fyne.Window
205
486
  }
206
487
 
207
- // IsOpen returns whether item is open
208
- func (a *Accordion) IsOpen(index int) bool {
209
- return a.openIndices[index]
488
+ func NewModal(content fyne.CanvasObject) *Modal {
489
+ m := &Modal{content: content}
490
+ m.ExtendBaseWidget(m)
491
+ return m
210
492
  }
211
493
 
212
- // Pagination represents a pagination component
213
- type Pagination struct {
214
- total int
215
- current int
494
+ func (m *Modal) Open(window fyne.Window) {
495
+ m.window = window
496
+ m.open = true
497
+ m.Show()
216
498
  }
217
499
 
218
- // NewPagination creates new pagination
219
- func NewPagination(total, current int) *Pagination {
220
- if current < 1 {
221
- current = 1
500
+ func (m *Modal) Close() { m.open = false; m.Hide() }
501
+ func (m *Modal) IsOpen() bool { return m.open }
502
+ func (m *Modal) Show() {
503
+ if m.window != nil {
504
+ popup := widget.NewModalPopUp(m.content, m.window.Canvas())
505
+ popup.Show()
222
506
  }
223
- if current > total {
224
- current = total
507
+ }
508
+ func (m *Modal) Hide() {}
509
+
510
+ // =============================================================================
511
+ // DROPDOWN
512
+ // =============================================================================
513
+
514
+ // Dropdown creates a dropdown selection widget using Fyne's Select widget.
515
+ func NewDropdown(items []string, onSelect func(string)) *widget.Select {
516
+ s := widget.NewSelect(items, onSelect)
517
+ return s
518
+ }
519
+
520
+ // =============================================================================
521
+ // TABS
522
+ // =============================================================================
523
+
524
+ func NewTabs(items ...*widget.TabItem) *container.AppTabs {
525
+ return container.NewAppTabs(items...)
526
+ }
527
+
528
+ // =============================================================================
529
+ // ACCORDION
530
+ // =============================================================================
531
+
532
+ type AccordionItem struct {
533
+ Title string
534
+ Content fyne.CanvasObject
535
+ }
536
+
537
+ type Accordion struct {
538
+ widget.BaseWidget
539
+ items []AccordionItem
540
+ openIndices map[int]bool
541
+ }
542
+
543
+ func NewAccordion(items ...AccordionItem) *Accordion {
544
+ a := &Accordion{items: items, openIndices: make(map[int]bool)}
545
+ a.ExtendBaseWidget(a)
546
+ return a
547
+ }
548
+
549
+ func (a *Accordion) Toggle(index int) { a.openIndices[index] = !a.openIndices[index]; a.Refresh() }
550
+ func (a *Accordion) Open(index int) { a.openIndices[index] = true; a.Refresh() }
551
+ func (a *Accordion) Close(index int) { delete(a.openIndices, index); a.Refresh() }
552
+ func (a *Accordion) OpenAll() {
553
+ for i := range a.items {
554
+ a.openIndices[i] = true
225
555
  }
226
- return &Pagination{total: total, current: current}
556
+ a.Refresh()
557
+ }
558
+ func (a *Accordion) CloseAll() { a.openIndices = make(map[int]bool); a.Refresh() }
559
+ func (a *Accordion) IsOpen(index int) bool { return a.openIndices[index] }
560
+
561
+ // =============================================================================
562
+ // PAGINATION
563
+ // =============================================================================
564
+
565
+ type Pagination struct {
566
+ widget.BaseWidget
567
+ total int
568
+ current int
569
+ onChange func(int)
570
+ }
571
+
572
+ func NewPagination(total, current int, onChange func(int)) *Pagination {
573
+ p := &Pagination{total: total, current: current, onChange: onChange}
574
+ p.ExtendBaseWidget(p)
575
+ return p
227
576
  }
228
577
 
229
- // GoTo navigates to a specific page
230
578
  func (p *Pagination) GoTo(page int) {
231
579
  if page >= 1 && page <= p.total {
232
580
  p.current = page
581
+ if p.onChange != nil {
582
+ p.onChange(page)
583
+ }
584
+ p.Refresh()
233
585
  }
234
586
  }
235
-
236
- // Next goes to next page
237
587
  func (p *Pagination) Next() {
238
588
  if p.current < p.total {
239
- p.current++
589
+ p.GoTo(p.current + 1)
240
590
  }
241
591
  }
242
-
243
- // Prev goes to previous page
244
592
  func (p *Pagination) Prev() {
245
593
  if p.current > 1 {
246
- p.current--
594
+ p.GoTo(p.current - 1)
247
595
  }
248
596
  }
597
+ func (p *Pagination) Current() int { return p.current }
598
+ func (p *Pagination) Total() int { return p.total }
599
+
600
+ // =============================================================================
601
+ // TOAST
602
+ // =============================================================================
603
+
604
+ type ToastType int
605
+
606
+ const (
607
+ ToastSuccess ToastType = iota
608
+ ToastError
609
+ ToastWarning
610
+ ToastInfo
611
+ )
612
+
613
+ func ShowToast(window fyne.Window, message string, toastType ToastType) {
614
+ c := DefaultColors()
615
+ var bgColor color.Color
616
+ var prefix string
617
+ switch toastType {
618
+ case ToastSuccess:
619
+ bgColor = c.Success
620
+ prefix = "✓ "
621
+ case ToastError:
622
+ bgColor = c.Error
623
+ prefix = "✕ "
624
+ case ToastWarning:
625
+ bgColor = c.Warning
626
+ prefix = "⚠ "
627
+ default:
628
+ bgColor = c.Info
629
+ prefix = "ℹ "
630
+ }
631
+ label := canvas.NewText(prefix+message, c.Text)
632
+ label.TextSize = 13
633
+ bg := canvas.NewRectangle(bgColor)
634
+ bg.CornerRadius = 10
635
+ cont := container.NewStack(bg, container.NewPadded(label))
636
+ popover := widget.NewPopUp(cont, window.Canvas())
637
+ popover.Move(fyne.NewPos(window.Canvas().Size().Width-320, window.Canvas().Size().Height-60))
638
+ }
639
+
640
+ // =============================================================================
641
+ // COMMAND PALETTE
642
+ // =============================================================================
643
+
644
+ type CommandItem struct {
645
+ Title, Subtitle, Kbd string
646
+ Action func()
647
+ }
648
+
649
+ // CommandPalette creates a modal command palette with search and filtering.
650
+ func NewCommandPalette(window fyne.Window, items []CommandItem) *fyne.Container {
651
+ c := DefaultColors()
652
+ searchEntry := widget.NewEntry()
653
+ searchEntry.SetPlaceHolder("Type to search commands...")
654
+
655
+ list := widget.NewList(
656
+ func() int { return len(items) },
657
+ func() fyne.CanvasObject {
658
+ return container.NewVBox(
659
+ widget.NewLabel("Command"),
660
+ widget.NewLabel("Description"),
661
+ )
662
+ },
663
+ func(id widget.ListItemID, item fyne.CanvasObject) {
664
+ if id < len(items) {
665
+ vbox := item.(*fyne.Container)
666
+ vbox.Objects[0].(*widget.Label).SetText(items[id].Title)
667
+ vbox.Objects[1].(*widget.Label).SetText(items[id].Subtitle)
668
+ }
669
+ },
670
+ )
671
+
672
+ searchEntry.OnChanged = func(query string) {
673
+ // Filter logic can be added here
674
+ list.Refresh()
675
+ }
676
+
677
+ list.OnSelected = func(id widget.ListItemID) {
678
+ if id < len(items) && items[id].Action != nil {
679
+ items[id].Action()
680
+ window.Hide()
681
+ }
682
+ }
249
683
 
250
- // Current returns current page
251
- func (p *Pagination) Current() int {
252
- return p.current
684
+ content := container.NewVBox(
685
+ searchEntry,
686
+ container.NewScroll(list),
687
+ )
688
+
689
+ bg := canvas.NewRectangle(c.Surface)
690
+ bg.CornerRadius = 14
691
+
692
+ return container.NewStack(bg, container.NewPadded(content))
253
693
  }
254
694
 
255
- // Total returns total pages
256
- func (p *Pagination) Total() int {
257
- return p.total
695
+ // =============================================================================
696
+ // SEARCH
697
+ // =============================================================================
698
+
699
+ type SearchItem struct {
700
+ Title, Subtitle string
701
+ Action func()
702
+ }
703
+
704
+ // Search creates a search widget with input and results list.
705
+ func NewSearch(items []SearchItem, onSelect func(SearchItem)) *fyne.Container {
706
+ c := DefaultColors()
707
+ searchEntry := widget.NewEntry()
708
+ searchEntry.SetPlaceHolder("Search...")
709
+
710
+ results := make([]SearchItem, 0)
711
+ resultsList := widget.NewList(
712
+ func() int { return len(results) },
713
+ func() fyne.CanvasObject {
714
+ return container.NewVBox(
715
+ widget.NewLabel("Title"),
716
+ widget.NewLabel("Subtitle"),
717
+ )
718
+ },
719
+ func(id widget.ListItemID, item fyne.CanvasObject) {
720
+ if id < len(results) {
721
+ vbox := item.(*fyne.Container)
722
+ vbox.Objects[0].(*widget.Label).SetText(results[id].Title)
723
+ vbox.Objects[1].(*widget.Label).SetText(results[id].Subtitle)
724
+ }
725
+ },
726
+ )
727
+
728
+ resultsList.OnSelected = func(id widget.ListItemID) {
729
+ if id < len(results) && onSelect != nil {
730
+ onSelect(results[id])
731
+ }
732
+ }
733
+
734
+ searchEntry.OnChanged = func(query string) {
735
+ results = make([]SearchItem, 0)
736
+ if query == "" {
737
+ resultsList.Refresh()
738
+ return
739
+ }
740
+ for _, item := range items {
741
+ // Simple case-insensitive search
742
+ if containsIgnoreCase(item.Title, query) || containsIgnoreCase(item.Subtitle, query) {
743
+ results = append(results, item)
744
+ }
745
+ }
746
+ resultsList.Refresh()
747
+ }
748
+
749
+ content := container.NewVBox(
750
+ searchEntry,
751
+ container.NewScroll(resultsList),
752
+ )
753
+
754
+ return content
258
755
  }
259
756
 
260
- // CommandPaletteItem represents a command palette item
261
- type CommandPaletteItem struct {
262
- Title string
263
- Kbd string
264
- Action func()
757
+ func containsIgnoreCase(s, substr string) bool {
758
+ return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
265
759
  }
266
760
 
267
- // CommandPalette represents a command palette
268
- type CommandPalette struct {
269
- open bool
270
- items []CommandPaletteItem
761
+ // =============================================================================
762
+ // TABLE
763
+ // =============================================================================
764
+
765
+ func NewTable(rows, cols int, cellFunc func(row, col int) fyne.CanvasObject) *widget.Table {
766
+ return widget.NewTable(
767
+ func() (int, int) { return rows, cols },
768
+ func() fyne.CanvasObject { return widget.NewLabel("") },
769
+ func(id widget.TableCellID, cell fyne.CanvasObject) {
770
+ if obj := cellFunc(id.Row, id.Col); obj != nil {
771
+ cell.(*widget.Label).SetText(obj.(*widget.Label).Text)
772
+ }
773
+ },
774
+ )
271
775
  }
272
776
 
273
- // NewCommandPalette creates a new command palette
274
- func NewCommandPalette() *CommandPalette {
275
- return &CommandPalette{items: make([]CommandPaletteItem, 0)}
777
+ // =============================================================================
778
+ // LIST
779
+ // =============================================================================
780
+
781
+ type ListItem struct {
782
+ Title, Subtitle string
276
783
  }
277
784
 
278
- // Open opens command palette
279
- func (c *CommandPalette) Open() {
280
- c.open = true
785
+ func NewList(items []ListItem) *widget.List {
786
+ return widget.NewList(
787
+ func() int { return len(items) },
788
+ func() fyne.CanvasObject { return widget.NewLabel("") },
789
+ func(id widget.ListItemID, item fyne.CanvasObject) {
790
+ item.(*widget.Label).SetText(items[id].Title)
791
+ },
792
+ )
281
793
  }
282
794
 
283
- // Close closes command palette
284
- func (c *CommandPalette) Close() {
285
- c.open = false
795
+ // =============================================================================
796
+ // LAYOUT HELPERS
797
+ // =============================================================================
798
+
799
+ func NewContainer(content fyne.CanvasObject) fyne.CanvasObject {
800
+ return container.NewMax(content)
286
801
  }
287
802
 
288
- // Toggle toggles command palette
289
- func (c *CommandPalette) Toggle() {
290
- c.open = !c.open
803
+ func NewFlex(objects ...fyne.CanvasObject) fyne.CanvasObject {
804
+ return container.NewHBox(objects...)
291
805
  }
292
806
 
293
- // IsOpen returns whether command palette is open
294
- func (c *CommandPalette) IsOpen() bool {
295
- return c.open
807
+ func NewStack(objects ...fyne.CanvasObject) fyne.CanvasObject {
808
+ return container.NewVBox(objects...)
296
809
  }
297
810
 
298
- // SetItems sets command items
299
- func (c *CommandPalette) SetItems(items []CommandPaletteItem) {
300
- c.items = items
811
+ func NewGrid(cols int, objects ...fyne.CanvasObject) fyne.CanvasObject {
812
+ return container.NewGridWithColumns(cols, objects...)
301
813
  }
302
814
 
303
- // Items returns all items
304
- func (c *CommandPalette) Items() []CommandPaletteItem {
305
- return c.items
815
+ func NewForm(items ...*widget.FormItem) *widget.Form {
816
+ return widget.NewForm(items...)
306
817
  }
307
818
 
308
- // Execute executes item by index
309
- func (c *CommandPalette) Execute(index int) {
310
- if index >= 0 && index < len(c.items) && c.items[index].Action != nil {
311
- c.items[index].Action()
312
- c.Close()
819
+ // =============================================================================
820
+ // NAV
821
+ // =============================================================================
822
+
823
+ // Nav creates a navigation sidebar widget.
824
+ func NewNav(items []string, active int, onSelect func(int)) *widget.List {
825
+ list := widget.NewList(
826
+ func() int { return len(items) },
827
+ func() fyne.CanvasObject {
828
+ return widget.NewLabel("Nav Item")
829
+ },
830
+ func(id widget.ListItemID, item fyne.CanvasObject) {
831
+ if id < len(items) {
832
+ item.(*widget.Label).SetText(items[id])
833
+ }
834
+ },
835
+ )
836
+ list.OnSelected = func(id widget.ListItemID) {
837
+ if onSelect != nil {
838
+ onSelect(id)
839
+ }
840
+ }
841
+ if active >= 0 && active < len(items) {
842
+ list.Select(active)
313
843
  }
844
+ return list
314
845
  }
315
846
 
316
- // SearchItem represents a search result item
317
- type SearchItem struct {
318
- Title string
319
- Subtitle string
320
- Action func()
321
- }
847
+ // =============================================================================
848
+ // STAT
849
+ // =============================================================================
322
850
 
323
- // Search represents a search component
324
- type Search struct {
325
- open bool
326
- items []SearchItem
327
- }
851
+ type StatDeltaType int
328
852
 
329
- // NewSearch creates a new search
330
- func NewSearch() *Search {
331
- return &Search{items: make([]SearchItem, 0)}
332
- }
853
+ const (
854
+ StatDeltaUp StatDeltaType = iota
855
+ StatDeltaDown
856
+ )
333
857
 
334
- // SetItems sets searchable items
335
- func (s *Search) SetItems(items []SearchItem) {
336
- s.items = items
337
- }
858
+ // Stat creates a stat/metric display widget with value, label, and optional delta.
859
+ func NewStat(value string, label string, delta string, deltaType StatDeltaType) *fyne.Container {
860
+ c := DefaultColors()
861
+
862
+ valueLabel := canvas.NewText(value, c.Text)
863
+ valueLabel.TextSize = 28
864
+ valueLabel.TextStyle = fyne.TextStyle{Bold: true}
865
+
866
+ labelWidget := canvas.NewText(label, c.TextMuted)
867
+ labelWidget.TextSize = 11
868
+
869
+ var deltaWidget *canvas.Text
870
+ if delta != "" {
871
+ deltaColor := c.SuccessText
872
+ if deltaType == StatDeltaDown {
873
+ deltaColor = c.ErrorText
874
+ }
875
+ prefix := "↑ "
876
+ if deltaType == StatDeltaDown {
877
+ prefix = "↓ "
878
+ }
879
+ deltaWidget = canvas.NewText(prefix+delta, deltaColor)
880
+ deltaWidget.TextSize = 11
881
+ }
338
882
 
339
- // Filter filters items by query
340
- func (s *Search) Filter(query string) []SearchItem {
341
- var results []SearchItem
342
- for _, item := range s.items {
343
- results = append(results, item)
883
+ content := container.NewVBox(
884
+ valueLabel,
885
+ labelWidget,
886
+ )
887
+
888
+ if deltaWidget != nil {
889
+ content = container.NewVBox(
890
+ valueLabel,
891
+ labelWidget,
892
+ deltaWidget,
893
+ )
344
894
  }
345
- return results
346
- }
347
895
 
348
- // Open opens search results
349
- func (s *Search) Open() {
350
- s.open = true
351
- }
896
+ bg := canvas.NewRectangle(c.Surface)
897
+ bg.StrokeColor = c.Border
898
+ bg.StrokeWidth = 1
899
+ bg.CornerRadius = 10
352
900
 
353
- // Close closes search results
354
- func (s *Search) Close() {
355
- s.open = false
901
+ return container.NewStack(bg, container.NewPadded(content))
356
902
  }
357
903
 
358
- // IsOpen returns whether search is open
359
- func (s *Search) IsOpen() bool {
360
- return s.open
904
+ // =============================================================================
905
+ // APPLICATION
906
+ // =============================================================================
907
+
908
+ type App struct {
909
+ fyne.App
910
+ Theme fyne.Theme
361
911
  }
362
912
 
363
- // Items returns all items
364
- func (s *Search) Items() []SearchItem {
365
- return s.items
913
+ func NewApp() *App {
914
+ a := app.New()
915
+ t := NewTheme()
916
+ a.Settings().SetTheme(t)
917
+ return &App{App: a, Theme: t}
366
918
  }
367
919
 
368
- // Select selects and executes item by index
369
- func (s *Search) Select(index int) {
370
- if index >= 0 && index < len(s.items) && s.items[index].Action != nil {
371
- s.items[index].Action()
372
- s.Close()
373
- }
920
+ func (a *App) NewWindow(title string) fyne.Window {
921
+ return a.App.NewWindow(title)
374
922
  }
375
923
 
376
- // Init initializes all CronixUI components
377
- func Init() {
378
- fmt.Println("CronixUI", Version, "initialized")
924
+ func Init() fyne.Theme {
925
+ return NewTheme()
379
926
  }