cronixui 1.0.4 → 1.0.5
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/package.json +5 -2
- package/packages/go/cronixui/cronixui.go +379 -0
- package/packages/go/cronixui/go.mod +10 -0
- package/packages/python/cronixui/__init__.py +35 -0
- package/packages/python/cronixui/accordion.py +29 -0
- package/packages/python/cronixui/command_palette.py +53 -0
- package/packages/python/cronixui/core.py +48 -0
- package/packages/python/cronixui/dropdown.py +26 -0
- package/packages/python/cronixui/modal.py +22 -0
- package/packages/python/cronixui/nav.py +31 -0
- package/packages/python/cronixui/pagination.py +58 -0
- package/packages/python/cronixui/pyproject.toml +11 -0
- package/packages/python/cronixui/search.py +50 -0
- package/packages/python/cronixui/tabs.py +18 -0
- package/packages/python/cronixui/toast.py +73 -0
- package/packages/python/cronixui/toggle.py +22 -0
- package/packages/rust/cronixui/src/accordion.rs +49 -0
- package/packages/rust/cronixui/src/command_palette.rs +62 -0
- package/packages/rust/cronixui/src/dropdown.rs +31 -0
- package/packages/rust/cronixui/src/lib.rs +79 -0
- package/packages/rust/cronixui/src/modal.rs +27 -0
- package/packages/rust/cronixui/src/pagination.rs +37 -0
- package/packages/rust/cronixui/src/search.rs +49 -0
- package/packages/rust/cronixui/src/tabs.rs +23 -0
- package/packages/rust/cronixui/src/toast.rs +70 -0
- package/packages/rust/cronixui/src/toggle.rs +27 -0
- package/packages/web/src/index.js +2 -0
- package/packages/web/src/index.mjs +2 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cronixui",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "CronixUI - A dark-themed UI toolkit with crimson accents and Outfit typography",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "CronixUI - A dark-themed UI toolkit with crimson accents and Outfit typography. Available for JavaScript, TypeScript, Python, Go, and Rust.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui",
|
|
7
7
|
"css",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
"packages/web/src/",
|
|
41
41
|
"packages/react/src/",
|
|
42
42
|
"packages/win/CronixUI.WinUI/",
|
|
43
|
+
"packages/python/cronixui/",
|
|
44
|
+
"packages/go/cronixui/",
|
|
45
|
+
"packages/rust/cronixui/src/",
|
|
43
46
|
"README.md"
|
|
44
47
|
],
|
|
45
48
|
"scripts": {
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
package cronixui
|
|
2
|
+
|
|
3
|
+
import "fmt"
|
|
4
|
+
|
|
5
|
+
// Version of CronixUI
|
|
6
|
+
const Version = "1.0.4"
|
|
7
|
+
|
|
8
|
+
// ToastType represents toast notification type
|
|
9
|
+
type ToastType int
|
|
10
|
+
|
|
11
|
+
const (
|
|
12
|
+
ToastTypeSuccess ToastType = iota
|
|
13
|
+
ToastTypeError
|
|
14
|
+
ToastTypeWarning
|
|
15
|
+
ToastTypeInfo
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// Toast represents a toast notification
|
|
19
|
+
type Toast struct {
|
|
20
|
+
Title string
|
|
21
|
+
Message string
|
|
22
|
+
Type ToastType
|
|
23
|
+
Duration int
|
|
24
|
+
}
|
|
25
|
+
|
|
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
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// WithTitle sets toast title
|
|
36
|
+
func WithTitle(title string) func(*Toast) {
|
|
37
|
+
return func(t *Toast) { t.Title = title }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// WithType sets toast type
|
|
41
|
+
func WithType(toastType ToastType) func(*Toast) {
|
|
42
|
+
return func(t *Toast) { t.Type = toastType }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// WithDuration sets toast duration
|
|
46
|
+
func WithDuration(duration int) func(*Toast) {
|
|
47
|
+
return func(t *Toast) { t.Duration = duration }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ToastSuccess shows a success toast
|
|
51
|
+
func ToastSuccess(message string) *Toast {
|
|
52
|
+
return ToastShow(message, WithType(ToastTypeSuccess))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ToastError shows an error toast
|
|
56
|
+
func ToastError(message string) *Toast {
|
|
57
|
+
return ToastShow(message, WithType(ToastTypeError))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ToastWarning shows a warning toast
|
|
61
|
+
func ToastWarning(message string) *Toast {
|
|
62
|
+
return ToastShow(message, WithType(ToastTypeWarning))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ToastInfo shows an info toast
|
|
66
|
+
func ToastInfo(message string) *Toast {
|
|
67
|
+
return ToastShow(message, WithType(ToastTypeInfo))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Toggle represents a toggle switch
|
|
71
|
+
type Toggle struct {
|
|
72
|
+
on bool
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// NewToggle creates a new toggle
|
|
76
|
+
func NewToggle() *Toggle {
|
|
77
|
+
return &Toggle{}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Toggle flips the toggle state
|
|
81
|
+
func (t *Toggle) Toggle() {
|
|
82
|
+
t.on = !t.on
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// IsOn returns current toggle state
|
|
86
|
+
func (t *Toggle) IsOn() bool {
|
|
87
|
+
return t.on
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// SetOn sets the toggle state
|
|
91
|
+
func (t *Toggle) SetOn(value bool) {
|
|
92
|
+
t.on = value
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Modal represents a modal dialog
|
|
96
|
+
type Modal struct {
|
|
97
|
+
open bool
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// NewModal creates a new modal
|
|
101
|
+
func NewModal() *Modal {
|
|
102
|
+
return &Modal{}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Open opens the modal
|
|
106
|
+
func (m *Modal) Open() {
|
|
107
|
+
m.open = true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Close closes the modal
|
|
111
|
+
func (m *Modal) Close() {
|
|
112
|
+
m.open = false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// IsOpen returns whether modal is open
|
|
116
|
+
func (m *Modal) IsOpen() bool {
|
|
117
|
+
return m.open
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Dropdown represents a dropdown menu
|
|
121
|
+
type Dropdown struct {
|
|
122
|
+
open bool
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// NewDropdown creates a new dropdown
|
|
126
|
+
func NewDropdown() *Dropdown {
|
|
127
|
+
return &Dropdown{}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Open opens the dropdown
|
|
131
|
+
func (d *Dropdown) Open() {
|
|
132
|
+
d.open = true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Close closes the dropdown
|
|
136
|
+
func (d *Dropdown) Close() {
|
|
137
|
+
d.open = false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Toggle flips dropdown state
|
|
141
|
+
func (d *Dropdown) Toggle() {
|
|
142
|
+
d.open = !d.open
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// IsOpen returns whether dropdown is open
|
|
146
|
+
func (d *Dropdown) IsOpen() bool {
|
|
147
|
+
return d.open
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Tabs represents a tabs component
|
|
151
|
+
type Tabs struct {
|
|
152
|
+
activeIndex int
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// NewTabs creates new tabs
|
|
156
|
+
func NewTabs() *Tabs {
|
|
157
|
+
return &Tabs{activeIndex: 0}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// SetActive sets active tab by index
|
|
161
|
+
func (t *Tabs) SetActive(index int) {
|
|
162
|
+
t.activeIndex = index
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ActiveIndex returns current active index
|
|
166
|
+
func (t *Tabs) ActiveIndex() int {
|
|
167
|
+
return t.activeIndex
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Accordion represents an accordion component
|
|
171
|
+
type Accordion struct {
|
|
172
|
+
openIndices map[int]bool
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// NewAccordion creates a new accordion
|
|
176
|
+
func NewAccordion() *Accordion {
|
|
177
|
+
return &Accordion{openIndices: make(map[int]bool)}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Toggle toggles an accordion item
|
|
181
|
+
func (a *Accordion) Toggle(index int) {
|
|
182
|
+
a.openIndices[index] = !a.openIndices[index]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Open opens an accordion item
|
|
186
|
+
func (a *Accordion) Open(index int) {
|
|
187
|
+
a.openIndices[index] = true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Close closes an accordion item
|
|
191
|
+
func (a *Accordion) Close(index int) {
|
|
192
|
+
a.openIndices[index] = false
|
|
193
|
+
}
|
|
194
|
+
|
|
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
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// CloseAll closes all accordion items
|
|
203
|
+
func (a *Accordion) CloseAll() {
|
|
204
|
+
a.openIndices = make(map[int]bool)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// IsOpen returns whether item is open
|
|
208
|
+
func (a *Accordion) IsOpen(index int) bool {
|
|
209
|
+
return a.openIndices[index]
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Pagination represents a pagination component
|
|
213
|
+
type Pagination struct {
|
|
214
|
+
total int
|
|
215
|
+
current int
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// NewPagination creates new pagination
|
|
219
|
+
func NewPagination(total, current int) *Pagination {
|
|
220
|
+
if current < 1 {
|
|
221
|
+
current = 1
|
|
222
|
+
}
|
|
223
|
+
if current > total {
|
|
224
|
+
current = total
|
|
225
|
+
}
|
|
226
|
+
return &Pagination{total: total, current: current}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// GoTo navigates to a specific page
|
|
230
|
+
func (p *Pagination) GoTo(page int) {
|
|
231
|
+
if page >= 1 && page <= p.total {
|
|
232
|
+
p.current = page
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Next goes to next page
|
|
237
|
+
func (p *Pagination) Next() {
|
|
238
|
+
if p.current < p.total {
|
|
239
|
+
p.current++
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Prev goes to previous page
|
|
244
|
+
func (p *Pagination) Prev() {
|
|
245
|
+
if p.current > 1 {
|
|
246
|
+
p.current--
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Current returns current page
|
|
251
|
+
func (p *Pagination) Current() int {
|
|
252
|
+
return p.current
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Total returns total pages
|
|
256
|
+
func (p *Pagination) Total() int {
|
|
257
|
+
return p.total
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// CommandPaletteItem represents a command palette item
|
|
261
|
+
type CommandPaletteItem struct {
|
|
262
|
+
Title string
|
|
263
|
+
Kbd string
|
|
264
|
+
Action func()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// CommandPalette represents a command palette
|
|
268
|
+
type CommandPalette struct {
|
|
269
|
+
open bool
|
|
270
|
+
items []CommandPaletteItem
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// NewCommandPalette creates a new command palette
|
|
274
|
+
func NewCommandPalette() *CommandPalette {
|
|
275
|
+
return &CommandPalette{items: make([]CommandPaletteItem, 0)}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Open opens command palette
|
|
279
|
+
func (c *CommandPalette) Open() {
|
|
280
|
+
c.open = true
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Close closes command palette
|
|
284
|
+
func (c *CommandPalette) Close() {
|
|
285
|
+
c.open = false
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Toggle toggles command palette
|
|
289
|
+
func (c *CommandPalette) Toggle() {
|
|
290
|
+
c.open = !c.open
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// IsOpen returns whether command palette is open
|
|
294
|
+
func (c *CommandPalette) IsOpen() bool {
|
|
295
|
+
return c.open
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// SetItems sets command items
|
|
299
|
+
func (c *CommandPalette) SetItems(items []CommandPaletteItem) {
|
|
300
|
+
c.items = items
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Items returns all items
|
|
304
|
+
func (c *CommandPalette) Items() []CommandPaletteItem {
|
|
305
|
+
return c.items
|
|
306
|
+
}
|
|
307
|
+
|
|
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()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// SearchItem represents a search result item
|
|
317
|
+
type SearchItem struct {
|
|
318
|
+
Title string
|
|
319
|
+
Subtitle string
|
|
320
|
+
Action func()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Search represents a search component
|
|
324
|
+
type Search struct {
|
|
325
|
+
open bool
|
|
326
|
+
items []SearchItem
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// NewSearch creates a new search
|
|
330
|
+
func NewSearch() *Search {
|
|
331
|
+
return &Search{items: make([]SearchItem, 0)}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// SetItems sets searchable items
|
|
335
|
+
func (s *Search) SetItems(items []SearchItem) {
|
|
336
|
+
s.items = items
|
|
337
|
+
}
|
|
338
|
+
|
|
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)
|
|
344
|
+
}
|
|
345
|
+
return results
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Open opens search results
|
|
349
|
+
func (s *Search) Open() {
|
|
350
|
+
s.open = true
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Close closes search results
|
|
354
|
+
func (s *Search) Close() {
|
|
355
|
+
s.open = false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// IsOpen returns whether search is open
|
|
359
|
+
func (s *Search) IsOpen() bool {
|
|
360
|
+
return s.open
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Items returns all items
|
|
364
|
+
func (s *Search) Items() []SearchItem {
|
|
365
|
+
return s.items
|
|
366
|
+
}
|
|
367
|
+
|
|
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
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Init initializes all CronixUI components
|
|
377
|
+
func Init() {
|
|
378
|
+
fmt.Println("CronixUI", Version, "initialized")
|
|
379
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "cronixui"
|
|
3
|
+
version = "1.0.2"
|
|
4
|
+
description = "CronixUI - A dark-themed UI toolkit with crimson accents and Outfit typography"
|
|
5
|
+
license = "GPL-3.0"
|
|
6
|
+
authors = ["CazyUndee"]
|
|
7
|
+
keywords = ["ui", "dark-mode", "design-system", "components"]
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
path = "cronixui.go"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CronixUI - A dark-themed UI toolkit with crimson accents and Outfit typography
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .toast import Toast
|
|
6
|
+
from .toggle import Toggle
|
|
7
|
+
from .modal import Modal
|
|
8
|
+
from .dropdown import Dropdown
|
|
9
|
+
from .tabs import Tabs
|
|
10
|
+
from .accordion import Accordion
|
|
11
|
+
from .pagination import Pagination
|
|
12
|
+
from .command_palette import CommandPalette, CommandPaletteItem
|
|
13
|
+
from .search import Search, SearchItem
|
|
14
|
+
from .nav import Nav
|
|
15
|
+
from .core import init, query, query_all, create_el
|
|
16
|
+
|
|
17
|
+
__version__ = "1.0.4"
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Toast",
|
|
20
|
+
"Toggle",
|
|
21
|
+
"Modal",
|
|
22
|
+
"Dropdown",
|
|
23
|
+
"Tabs",
|
|
24
|
+
"Accordion",
|
|
25
|
+
"Pagination",
|
|
26
|
+
"CommandPalette",
|
|
27
|
+
"CommandPaletteItem",
|
|
28
|
+
"Search",
|
|
29
|
+
"SearchItem",
|
|
30
|
+
"Nav",
|
|
31
|
+
"init",
|
|
32
|
+
"query",
|
|
33
|
+
"query_all",
|
|
34
|
+
"create_el",
|
|
35
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Accordion component."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Accordion:
|
|
5
|
+
"""Accordion component."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, element=None):
|
|
8
|
+
"""Initialize accordion on element."""
|
|
9
|
+
self._element = element
|
|
10
|
+
self._open_indices: list = []
|
|
11
|
+
|
|
12
|
+
def toggle(self, index: int) -> None:
|
|
13
|
+
"""Toggle accordion item by index."""
|
|
14
|
+
if index in self._open_indices:
|
|
15
|
+
self._open_indices.remove(index)
|
|
16
|
+
else:
|
|
17
|
+
self._open_indices.append(index)
|
|
18
|
+
|
|
19
|
+
def open_all(self, total: int) -> None:
|
|
20
|
+
"""Open all accordion items."""
|
|
21
|
+
self._open_indices = list(range(total))
|
|
22
|
+
|
|
23
|
+
def close_all(self) -> None:
|
|
24
|
+
"""Close all accordion items."""
|
|
25
|
+
self._open_indices.clear()
|
|
26
|
+
|
|
27
|
+
def is_open(self, index: int) -> bool:
|
|
28
|
+
"""Check if item is open."""
|
|
29
|
+
return index in self._open_indices
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Command palette component."""
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Optional, List
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class CommandPaletteItem:
|
|
9
|
+
"""Command palette item."""
|
|
10
|
+
|
|
11
|
+
title: str
|
|
12
|
+
kbd: Optional[str] = None
|
|
13
|
+
action: Optional[Callable] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CommandPalette:
|
|
17
|
+
"""Command palette component."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, element=None):
|
|
20
|
+
"""Initialize command palette on element."""
|
|
21
|
+
self._element = element
|
|
22
|
+
self._items: List[CommandPaletteItem] = []
|
|
23
|
+
self._open: bool = False
|
|
24
|
+
|
|
25
|
+
def open(self) -> None:
|
|
26
|
+
"""Open command palette."""
|
|
27
|
+
self._open = True
|
|
28
|
+
|
|
29
|
+
def close(self) -> None:
|
|
30
|
+
"""Close command palette."""
|
|
31
|
+
self._open = False
|
|
32
|
+
|
|
33
|
+
def toggle(self) -> None:
|
|
34
|
+
"""Toggle command palette."""
|
|
35
|
+
self._open = not self._open
|
|
36
|
+
|
|
37
|
+
def is_open(self) -> bool:
|
|
38
|
+
"""Check if command palette is open."""
|
|
39
|
+
return self._open
|
|
40
|
+
|
|
41
|
+
def set_items(self, items: List[CommandPaletteItem]) -> None:
|
|
42
|
+
"""Set command items."""
|
|
43
|
+
self._items = items
|
|
44
|
+
|
|
45
|
+
def get_items(self) -> List[CommandPaletteItem]:
|
|
46
|
+
"""Get all items."""
|
|
47
|
+
return self._items
|
|
48
|
+
|
|
49
|
+
def execute(self, index: int) -> None:
|
|
50
|
+
"""Execute item by index."""
|
|
51
|
+
if 0 <= index < len(self._items) and self._items[index].action:
|
|
52
|
+
self._items[index].action()
|
|
53
|
+
self.close()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Core CronixUI functions."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Optional, List, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def init() -> None:
|
|
7
|
+
"""Initialize all CronixUI components."""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def query(selector: str) -> Optional[Any]:
|
|
12
|
+
"""Query single element.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
selector: CSS selector
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Element or None
|
|
19
|
+
"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def query_all(selector: str) -> List[Any]:
|
|
24
|
+
"""Query all matching elements.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
selector: CSS selector
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of elements
|
|
31
|
+
"""
|
|
32
|
+
return []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def create_el(
|
|
36
|
+
tag: str, class_name: Optional[str] = None, attrs: Optional[Dict[str, str]] = None
|
|
37
|
+
) -> Any:
|
|
38
|
+
"""Create an element.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
tag: HTML tag name
|
|
42
|
+
class_name: Optional CSS class name
|
|
43
|
+
attrs: Optional attributes dict
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Created element
|
|
47
|
+
"""
|
|
48
|
+
pass
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Dropdown menu component."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Dropdown:
|
|
5
|
+
"""Dropdown menu component."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, element=None):
|
|
8
|
+
"""Initialize dropdown on element."""
|
|
9
|
+
self._element = element
|
|
10
|
+
self._open: bool = False
|
|
11
|
+
|
|
12
|
+
def open(self) -> None:
|
|
13
|
+
"""Open the dropdown."""
|
|
14
|
+
self._open = True
|
|
15
|
+
|
|
16
|
+
def close(self) -> None:
|
|
17
|
+
"""Close the dropdown."""
|
|
18
|
+
self._open = False
|
|
19
|
+
|
|
20
|
+
def toggle(self) -> None:
|
|
21
|
+
"""Toggle the dropdown."""
|
|
22
|
+
self._open = not self._open
|
|
23
|
+
|
|
24
|
+
def is_open(self) -> bool:
|
|
25
|
+
"""Check if dropdown is open."""
|
|
26
|
+
return self._open
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Modal dialog component."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Modal:
|
|
5
|
+
"""Modal dialog component."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, element=None):
|
|
8
|
+
"""Initialize modal on element."""
|
|
9
|
+
self._element = element
|
|
10
|
+
self._open: bool = False
|
|
11
|
+
|
|
12
|
+
def open(self) -> None:
|
|
13
|
+
"""Open the modal."""
|
|
14
|
+
self._open = True
|
|
15
|
+
|
|
16
|
+
def close(self) -> None:
|
|
17
|
+
"""Close the modal."""
|
|
18
|
+
self._open = False
|
|
19
|
+
|
|
20
|
+
def is_open(self) -> bool:
|
|
21
|
+
"""Check if modal is open."""
|
|
22
|
+
return self._open
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Navigation component."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Nav:
|
|
5
|
+
"""Navigation component."""
|
|
6
|
+
|
|
7
|
+
_instances = []
|
|
8
|
+
|
|
9
|
+
def __init__(self, element=None):
|
|
10
|
+
"""Initialize navigation on element."""
|
|
11
|
+
self._element = element
|
|
12
|
+
self._active: str = ""
|
|
13
|
+
Nav._instances.append(self)
|
|
14
|
+
|
|
15
|
+
def set_active(self, item: str) -> None:
|
|
16
|
+
"""Set active navigation item."""
|
|
17
|
+
self._active = item
|
|
18
|
+
|
|
19
|
+
def get_active(self) -> str:
|
|
20
|
+
"""Get active navigation item."""
|
|
21
|
+
return self._active
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def init(cls) -> None:
|
|
25
|
+
"""Initialize all navigation components."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def get_all(cls) -> list:
|
|
30
|
+
"""Get all navigation instances."""
|
|
31
|
+
return cls._instances
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Pagination component."""
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Pagination:
|
|
7
|
+
"""Pagination component."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
element=None,
|
|
12
|
+
total: int = 1,
|
|
13
|
+
current: int = 1,
|
|
14
|
+
on_change: Optional[Callable[[int], None]] = None,
|
|
15
|
+
):
|
|
16
|
+
"""Initialize pagination.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
element: Target element
|
|
20
|
+
total: Total number of pages
|
|
21
|
+
current: Current page (default: 1)
|
|
22
|
+
on_change: Callback when page changes
|
|
23
|
+
"""
|
|
24
|
+
self._element = element
|
|
25
|
+
self._total = total
|
|
26
|
+
self._current = current
|
|
27
|
+
self._on_change = on_change
|
|
28
|
+
|
|
29
|
+
def go_to(self, page: int) -> None:
|
|
30
|
+
"""Go to specific page."""
|
|
31
|
+
if 1 <= page <= self._total:
|
|
32
|
+
self._current = page
|
|
33
|
+
if self._on_change:
|
|
34
|
+
self._on_change(page)
|
|
35
|
+
|
|
36
|
+
def next(self) -> None:
|
|
37
|
+
"""Go to next page."""
|
|
38
|
+
if self._current < self._total:
|
|
39
|
+
self.go_to(self._current + 1)
|
|
40
|
+
|
|
41
|
+
def prev(self) -> None:
|
|
42
|
+
"""Go to previous page."""
|
|
43
|
+
if self._current > 1:
|
|
44
|
+
self.go_to(self._current - 1)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def current(self) -> int:
|
|
48
|
+
"""Get current page."""
|
|
49
|
+
return self._current
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def total(self) -> int:
|
|
53
|
+
"""Get total pages."""
|
|
54
|
+
return self._total
|
|
55
|
+
|
|
56
|
+
def render(self) -> None:
|
|
57
|
+
"""Re-render pagination."""
|
|
58
|
+
pass
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cronixui"
|
|
3
|
+
version = "1.0.2"
|
|
4
|
+
description = "CronixUI - A dark-themed UI toolkit with crimson accents and Outfit typography"
|
|
5
|
+
license = {text = "GPL-3.0"}
|
|
6
|
+
authors = [{name = "CazyUndee"}]
|
|
7
|
+
keywords = ["ui", "dark-mode", "design-system", "components"]
|
|
8
|
+
requires-python = ">=3.8"
|
|
9
|
+
|
|
10
|
+
[project.urls]
|
|
11
|
+
Repository = "https://github.com/CazyUndee/CronixUI"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Search component."""
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Optional, List
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class SearchItem:
|
|
9
|
+
"""Search result item."""
|
|
10
|
+
|
|
11
|
+
title: str
|
|
12
|
+
subtitle: Optional[str] = None
|
|
13
|
+
action: Optional[Callable] = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Search:
|
|
17
|
+
"""Search component."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, element=None):
|
|
20
|
+
"""Initialize search on element."""
|
|
21
|
+
self._element = element
|
|
22
|
+
self._items: List[SearchItem] = []
|
|
23
|
+
self._open: bool = False
|
|
24
|
+
|
|
25
|
+
def set_items(self, items: List[SearchItem]) -> None:
|
|
26
|
+
"""Set searchable items."""
|
|
27
|
+
self._items = items
|
|
28
|
+
|
|
29
|
+
def filter(self, query: str) -> List[SearchItem]:
|
|
30
|
+
"""Filter items by query."""
|
|
31
|
+
query_lower = query.lower()
|
|
32
|
+
return [item for item in self._items if query_lower in item.title.lower()]
|
|
33
|
+
|
|
34
|
+
def open(self) -> None:
|
|
35
|
+
"""Open search results."""
|
|
36
|
+
self._open = True
|
|
37
|
+
|
|
38
|
+
def close(self) -> None:
|
|
39
|
+
"""Close search results."""
|
|
40
|
+
self._open = False
|
|
41
|
+
|
|
42
|
+
def is_open(self) -> bool:
|
|
43
|
+
"""Check if search is open."""
|
|
44
|
+
return self._open
|
|
45
|
+
|
|
46
|
+
def select(self, index: int) -> None:
|
|
47
|
+
"""Select and execute item by index."""
|
|
48
|
+
if 0 <= index < len(self._items) and self._items[index].action:
|
|
49
|
+
self._items[index].action()
|
|
50
|
+
self.close()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Tabs component."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Tabs:
|
|
5
|
+
"""Tabs component."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, element=None):
|
|
8
|
+
"""Initialize tabs on element."""
|
|
9
|
+
self._element = element
|
|
10
|
+
self._active_index: int = 0
|
|
11
|
+
|
|
12
|
+
def set_active(self, index: int) -> None:
|
|
13
|
+
"""Set active tab by index."""
|
|
14
|
+
self._active_index = index
|
|
15
|
+
|
|
16
|
+
def active_index(self) -> int:
|
|
17
|
+
"""Get active tab index."""
|
|
18
|
+
return self._active_index
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Toast notification component."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ToastType(Enum):
|
|
8
|
+
SUCCESS = "success"
|
|
9
|
+
ERROR = "error"
|
|
10
|
+
WARNING = "warning"
|
|
11
|
+
INFO = "info"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Toast:
|
|
15
|
+
"""Toast notification component."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, element=None):
|
|
18
|
+
self._element = element
|
|
19
|
+
self._title: Optional[str] = None
|
|
20
|
+
self._message: str = ""
|
|
21
|
+
self._type: ToastType = ToastType.INFO
|
|
22
|
+
self._duration: int = 4000
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def show(
|
|
26
|
+
title: Optional[str] = None,
|
|
27
|
+
message: str = "",
|
|
28
|
+
type: str = "info",
|
|
29
|
+
duration: int = 4000,
|
|
30
|
+
) -> "Toast":
|
|
31
|
+
"""Show a toast notification."""
|
|
32
|
+
toast = Toast()
|
|
33
|
+
toast._title = title
|
|
34
|
+
toast._message = message
|
|
35
|
+
toast._type = ToastType(type)
|
|
36
|
+
toast._duration = duration
|
|
37
|
+
return toast
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def success(message: str, title: Optional[str] = None) -> "Toast":
|
|
41
|
+
"""Show a success toast."""
|
|
42
|
+
return Toast.show(title=title, message=message, type="success")
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def error(message: str, title: Optional[str] = None) -> "Toast":
|
|
46
|
+
"""Show an error toast."""
|
|
47
|
+
return Toast.show(title=title, message=message, type="error")
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def warning(message: str, title: Optional[str] = None) -> "Toast":
|
|
51
|
+
"""Show a warning toast."""
|
|
52
|
+
return Toast.show(title=title, message=message, type="warning")
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def info(message: str, title: Optional[str] = None) -> "Toast":
|
|
56
|
+
"""Show an info toast."""
|
|
57
|
+
return Toast.show(title=title, message=message, type="info")
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def title(self) -> Optional[str]:
|
|
61
|
+
return self._title
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def message(self) -> str:
|
|
65
|
+
return self._message
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def type(self) -> ToastType:
|
|
69
|
+
return self._type
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def duration(self) -> int:
|
|
73
|
+
return self._duration
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Toggle switch component."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Toggle:
|
|
5
|
+
"""Toggle switch component."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, element=None):
|
|
8
|
+
"""Initialize toggle on element."""
|
|
9
|
+
self._element = element
|
|
10
|
+
self._on: bool = False
|
|
11
|
+
|
|
12
|
+
def toggle(self) -> None:
|
|
13
|
+
"""Toggle the state."""
|
|
14
|
+
self._on = not self._on
|
|
15
|
+
|
|
16
|
+
def is_on(self) -> bool:
|
|
17
|
+
"""Check if toggle is on."""
|
|
18
|
+
return self._on
|
|
19
|
+
|
|
20
|
+
def set_on(self, value: bool) -> None:
|
|
21
|
+
"""Set toggle state."""
|
|
22
|
+
self._on = value
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
|
|
3
|
+
pub struct Accordion {
|
|
4
|
+
open_indices: HashSet<usize>,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
impl Accordion {
|
|
8
|
+
pub fn new() -> Self {
|
|
9
|
+
Self {
|
|
10
|
+
open_indices: HashSet::new(),
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn toggle(&mut self, index: usize) {
|
|
15
|
+
if self.open_indices.contains(&index) {
|
|
16
|
+
self.open_indices.remove(&index);
|
|
17
|
+
} else {
|
|
18
|
+
self.open_indices.insert(index);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub fn open(&mut self, index: usize) {
|
|
23
|
+
self.open_indices.insert(index);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn close(&mut self, index: usize) {
|
|
27
|
+
self.open_indices.remove(&index);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub fn open_all(&mut self, total: usize) {
|
|
31
|
+
for i in 0..total {
|
|
32
|
+
self.open_indices.insert(i);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn close_all(&mut self) {
|
|
37
|
+
self.open_indices.clear();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn is_open(&self, index: usize) -> bool {
|
|
41
|
+
self.open_indices.contains(&index)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
impl Default for Accordion {
|
|
46
|
+
fn default() -> Self {
|
|
47
|
+
Self::new()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
pub struct CommandPaletteItem {
|
|
2
|
+
pub title: String,
|
|
3
|
+
pub kbd: Option<String>,
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
impl CommandPaletteItem {
|
|
7
|
+
pub fn new(title: impl Into<String>) -> Self {
|
|
8
|
+
Self {
|
|
9
|
+
title: title.into(),
|
|
10
|
+
kbd: None,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn kbd(mut self, kbd: impl Into<String>) -> Self {
|
|
15
|
+
self.kbd = Some(kbd.into());
|
|
16
|
+
self
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub struct CommandPalette {
|
|
21
|
+
open: bool,
|
|
22
|
+
items: Vec<CommandPaletteItem>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl CommandPalette {
|
|
26
|
+
pub fn new() -> Self {
|
|
27
|
+
Self {
|
|
28
|
+
open: false,
|
|
29
|
+
items: Vec::new(),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn open(&mut self) {
|
|
34
|
+
self.open = true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub fn close(&mut self) {
|
|
38
|
+
self.open = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn toggle(&mut self) {
|
|
42
|
+
self.open = !self.open;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub fn is_open(&self) -> bool {
|
|
46
|
+
self.open
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn set_items(&mut self, items: Vec<CommandPaletteItem>) {
|
|
50
|
+
self.items = items;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn items(&self) -> &[CommandPaletteItem] {
|
|
54
|
+
&self.items
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl Default for CommandPalette {
|
|
59
|
+
fn default() -> Self {
|
|
60
|
+
Self::new()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
pub struct Dropdown {
|
|
2
|
+
open: bool,
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
impl Dropdown {
|
|
6
|
+
pub fn new() -> Self {
|
|
7
|
+
Self { open: false }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
pub fn open(&mut self) {
|
|
11
|
+
self.open = true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn close(&mut self) {
|
|
15
|
+
self.open = false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn toggle(&mut self) {
|
|
19
|
+
self.open = !self.open;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub fn is_open(&self) -> bool {
|
|
23
|
+
self.open
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl Default for Dropdown {
|
|
28
|
+
fn default() -> Self {
|
|
29
|
+
Self::new()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//! CronixUI - A dark-themed UI toolkit with crimson accents and Outfit typography
|
|
2
|
+
//!
|
|
3
|
+
//! ## Example
|
|
4
|
+
//!
|
|
5
|
+
//! ```rust
|
|
6
|
+
//! use cronixui::{Toast, Toggle, Modal};
|
|
7
|
+
//!
|
|
8
|
+
//! // Create a toast
|
|
9
|
+
//! let toast = Toast::success("Operation completed!");
|
|
10
|
+
//!
|
|
11
|
+
//! // Create a toggle
|
|
12
|
+
//! let mut toggle = Toggle::new();
|
|
13
|
+
//! toggle.toggle();
|
|
14
|
+
//! assert!(toggle.is_on());
|
|
15
|
+
//! ```
|
|
16
|
+
|
|
17
|
+
pub const VERSION: &str = "1.0.4";
|
|
18
|
+
|
|
19
|
+
mod toast;
|
|
20
|
+
mod toggle;
|
|
21
|
+
mod modal;
|
|
22
|
+
mod dropdown;
|
|
23
|
+
mod tabs;
|
|
24
|
+
mod accordion;
|
|
25
|
+
mod pagination;
|
|
26
|
+
mod command_palette;
|
|
27
|
+
mod search;
|
|
28
|
+
|
|
29
|
+
pub use toast::{Toast, ToastType};
|
|
30
|
+
pub use toggle::Toggle;
|
|
31
|
+
pub use modal::Modal;
|
|
32
|
+
pub use dropdown::Dropdown;
|
|
33
|
+
pub use tabs::Tabs;
|
|
34
|
+
pub use accordion::Accordion;
|
|
35
|
+
pub use pagination::Pagination;
|
|
36
|
+
pub use command_palette::{CommandPalette, CommandPaletteItem};
|
|
37
|
+
pub use search::{Search, SearchItem};
|
|
38
|
+
|
|
39
|
+
/// Initialize CronixUI
|
|
40
|
+
pub fn init() {
|
|
41
|
+
println!("CronixUI {} initialized", VERSION);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[cfg(test)]
|
|
45
|
+
mod tests {
|
|
46
|
+
use super::*;
|
|
47
|
+
|
|
48
|
+
#[test]
|
|
49
|
+
fn test_toggle() {
|
|
50
|
+
let mut toggle = Toggle::new();
|
|
51
|
+
assert!(!toggle.is_on());
|
|
52
|
+
toggle.toggle();
|
|
53
|
+
assert!(toggle.is_on());
|
|
54
|
+
toggle.set_on(false);
|
|
55
|
+
assert!(!toggle.is_on());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[test]
|
|
59
|
+
fn test_pagination() {
|
|
60
|
+
let mut pagination = Pagination::new(10, 1);
|
|
61
|
+
assert_eq!(pagination.current(), 1);
|
|
62
|
+
pagination.next();
|
|
63
|
+
assert_eq!(pagination.current(), 2);
|
|
64
|
+
pagination.go_to(5);
|
|
65
|
+
assert_eq!(pagination.current(), 5);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#[test]
|
|
69
|
+
fn test_search() {
|
|
70
|
+
let mut search = Search::new();
|
|
71
|
+
search.set_items(vec![
|
|
72
|
+
SearchItem::new("Apple"),
|
|
73
|
+
SearchItem::new("Banana"),
|
|
74
|
+
SearchItem::new("Apricot"),
|
|
75
|
+
]);
|
|
76
|
+
let results = search.filter("ap");
|
|
77
|
+
assert_eq!(results.len(), 2);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
pub struct Modal {
|
|
2
|
+
open: bool,
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
impl Modal {
|
|
6
|
+
pub fn new() -> Self {
|
|
7
|
+
Self { open: false }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
pub fn open(&mut self) {
|
|
11
|
+
self.open = true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn close(&mut self) {
|
|
15
|
+
self.open = false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn is_open(&self) -> bool {
|
|
19
|
+
self.open
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl Default for Modal {
|
|
24
|
+
fn default() -> Self {
|
|
25
|
+
Self::new()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
pub struct Pagination {
|
|
2
|
+
total: usize,
|
|
3
|
+
current: usize,
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
impl Pagination {
|
|
7
|
+
pub fn new(total: usize, current: usize) -> Self {
|
|
8
|
+
let current = current.clamp(1, total.max(1));
|
|
9
|
+
Self { total, current }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
pub fn go_to(&mut self, page: usize) {
|
|
13
|
+
if page >= 1 && page <= self.total {
|
|
14
|
+
self.current = page;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn current(&self) -> usize {
|
|
19
|
+
self.current
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub fn total(&self) -> usize {
|
|
23
|
+
self.total
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn next(&mut self) {
|
|
27
|
+
if self.current < self.total {
|
|
28
|
+
self.current += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn prev(&mut self) {
|
|
33
|
+
if self.current > 1 {
|
|
34
|
+
self.current -= 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
pub struct SearchItem {
|
|
2
|
+
pub title: String,
|
|
3
|
+
pub subtitle: Option<String>,
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
impl SearchItem {
|
|
7
|
+
pub fn new(title: impl Into<String>) -> Self {
|
|
8
|
+
Self {
|
|
9
|
+
title: title.into(),
|
|
10
|
+
subtitle: None,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
|
|
15
|
+
self.subtitle = Some(subtitle.into());
|
|
16
|
+
self
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub struct Search {
|
|
21
|
+
items: Vec<SearchItem>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl Search {
|
|
25
|
+
pub fn new() -> Self {
|
|
26
|
+
Self { items: Vec::new() }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub fn set_items(&mut self, items: Vec<SearchItem>) {
|
|
30
|
+
self.items = items;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn filter(&self, query: &str) -> Vec<&SearchItem> {
|
|
34
|
+
self.items
|
|
35
|
+
.iter()
|
|
36
|
+
.filter(|item| item.title.to_lowercase().contains(&query.to_lowercase()))
|
|
37
|
+
.collect()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn items(&self) -> &[SearchItem] {
|
|
41
|
+
&self.items
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
impl Default for Search {
|
|
46
|
+
fn default() -> Self {
|
|
47
|
+
Self::new()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
pub struct Tabs {
|
|
2
|
+
active_index: usize,
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
impl Tabs {
|
|
6
|
+
pub fn new() -> Self {
|
|
7
|
+
Self { active_index: 0 }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
pub fn set_active(&mut self, index: usize) {
|
|
11
|
+
self.active_index = index;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn active_index(&self) -> usize {
|
|
15
|
+
self.active_index
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl Default for Tabs {
|
|
20
|
+
fn default() -> Self {
|
|
21
|
+
Self::new()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
use std::fmt;
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
4
|
+
pub enum ToastType {
|
|
5
|
+
Success,
|
|
6
|
+
Error,
|
|
7
|
+
Warning,
|
|
8
|
+
Info,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
impl fmt::Display for ToastType {
|
|
12
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
13
|
+
match self {
|
|
14
|
+
ToastType::Success => write!(f, "success"),
|
|
15
|
+
ToastType::Error => write!(f, "error"),
|
|
16
|
+
ToastType::Warning => write!(f, "warning"),
|
|
17
|
+
ToastType::Info => write!(f, "info"),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Debug, Clone)]
|
|
23
|
+
pub struct Toast {
|
|
24
|
+
pub title: Option<String>,
|
|
25
|
+
pub message: String,
|
|
26
|
+
pub toast_type: ToastType,
|
|
27
|
+
pub duration: u32,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl Toast {
|
|
31
|
+
pub fn show(message: impl Into<String>) -> Self {
|
|
32
|
+
Self {
|
|
33
|
+
title: None,
|
|
34
|
+
message: message.into(),
|
|
35
|
+
toast_type: ToastType::Info,
|
|
36
|
+
duration: 4000,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn title(mut self, title: impl Into<String>) -> Self {
|
|
41
|
+
self.title = Some(title.into());
|
|
42
|
+
self
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub fn toast_type(mut self, toast_type: ToastType) -> Self {
|
|
46
|
+
self.toast_type = toast_type;
|
|
47
|
+
self
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub fn duration(mut self, duration: u32) -> Self {
|
|
51
|
+
self.duration = duration;
|
|
52
|
+
self
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pub fn success(message: impl Into<String>) -> Self {
|
|
56
|
+
Self::show(message).toast_type(ToastType::Success)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub fn error(message: impl Into<String>) -> Self {
|
|
60
|
+
Self::show(message).toast_type(ToastType::Error)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub fn warning(message: impl Into<String>) -> Self {
|
|
64
|
+
Self::show(message).toast_type(ToastType::Warning)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
pub fn info(message: impl Into<String>) -> Self {
|
|
68
|
+
Self::show(message).toast_type(ToastType::Info)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
pub struct Toggle {
|
|
2
|
+
on: bool,
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
impl Toggle {
|
|
6
|
+
pub fn new() -> Self {
|
|
7
|
+
Self { on: false }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
pub fn toggle(&mut self) {
|
|
11
|
+
self.on = !self.on;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub fn is_on(&self) -> bool {
|
|
15
|
+
self.on
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn set_on(&mut self, value: bool) {
|
|
19
|
+
self.on = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl Default for Toggle {
|
|
24
|
+
fn default() -> Self {
|
|
25
|
+
Self::new()
|
|
26
|
+
}
|
|
27
|
+
}
|