SaaS-FrameWork 0.1.1__py3-none-any.whl
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.
- components/__init__.py +119 -0
- components/buttons/DEx_BtnFAB.py +37 -0
- components/buttons/DEx_BtnFilled.py +20 -0
- components/buttons/DEx_BtnGhost.py +20 -0
- components/buttons/DEx_BtnIcon.py +18 -0
- components/buttons/DEx_BtnPrimary.py +21 -0
- components/buttons/DEx_BtnSegmented.py +45 -0
- components/buttons/DEx_BtnText.py +19 -0
- components/buttons/DEx_BtnTonal.py +20 -0
- components/buttons/DEx_MenuBar.py +61 -0
- components/buttons/DEx_PopupMenu.py +43 -0
- components/buttons/__init__.py +0 -0
- components/cards/DEx_Card.py +23 -0
- components/cards/DEx_ContactCard.py +52 -0
- components/cards/DEx_MetricCard.py +36 -0
- components/cards/__init__.py +0 -0
- components/data/DEx_DataTable.py +82 -0
- components/data/DEx_ExpansionPanel.py +39 -0
- components/data/DEx_ExpansionTile.py +29 -0
- components/data/DEx_GridView.py +65 -0
- components/data/DEx_ListTile.py +27 -0
- components/data/DEx_ListView.py +65 -0
- components/data/DEx_PipelineColumn.py +74 -0
- components/data/DEx_TreeView.py +536 -0
- components/data/__init__.py +0 -0
- components/feedback/DEx_AlertDialog.py +26 -0
- components/feedback/DEx_Banner.py +19 -0
- components/feedback/DEx_BottomSheet.py +37 -0
- components/feedback/DEx_Notify.py +15 -0
- components/feedback/DEx_ProgressBar.py +34 -0
- components/feedback/DEx_ProgressRing.py +52 -0
- components/feedback/DEx_Shimmer.py +84 -0
- components/feedback/DEx_Tooltip.py +24 -0
- components/feedback/__init__.py +0 -0
- components/inputs/DEx_AutoComplete.py +29 -0
- components/inputs/DEx_Checkbox.py +27 -0
- components/inputs/DEx_Chip.py +33 -0
- components/inputs/DEx_DatePicker.py +72 -0
- components/inputs/DEx_Dropdown.py +25 -0
- components/inputs/DEx_FilePicker.py +70 -0
- components/inputs/DEx_Radio.py +37 -0
- components/inputs/DEx_SearchBar.py +37 -0
- components/inputs/DEx_SearchField.py +9 -0
- components/inputs/DEx_Slider.py +49 -0
- components/inputs/DEx_Switch.py +22 -0
- components/inputs/DEx_TextField.py +29 -0
- components/inputs/DEx_TimePicker.py +71 -0
- components/inputs/__init__.py +0 -0
- components/layout/DEx_AnimatedSwitcher.py +43 -0
- components/layout/DEx_AppBar.py +48 -0
- components/layout/DEx_AppShell.py +80 -0
- components/layout/DEx_Draggable.py +67 -0
- components/layout/DEx_GestureDetector.py +52 -0
- components/layout/DEx_Header.py +57 -0
- components/layout/DEx_NavItem.py +61 -0
- components/layout/DEx_NavigationBar.py +30 -0
- components/layout/DEx_NavigationDrawer.py +60 -0
- components/layout/DEx_NavigationRail.py +42 -0
- components/layout/DEx_ResponsiveRow.py +42 -0
- components/layout/DEx_Sidebar.py +131 -0
- components/layout/DEx_Stack.py +73 -0
- components/layout/DEx_Tabs.py +110 -0
- components/layout/__init__.py +0 -0
- components/metadata.py +98 -0
- components/primitives/DEx_Avatar.py +87 -0
- components/primitives/DEx_Badge.py +18 -0
- components/primitives/DEx_Divider.py +13 -0
- components/primitives/DEx_Icon.py +46 -0
- components/primitives/DEx_Image.py +55 -0
- components/primitives/DEx_Markdown.py +21 -0
- components/primitives/DEx_SectionEyebrow.py +22 -0
- components/primitives/DEx_StatusDot.py +8 -0
- components/primitives/__init__.py +0 -0
- components/project.py +23 -0
- components/theme/DEx_ColorThemes.py +375 -0
- components/theme/DEx_Theme.py +136 -0
- components/theme/__init__.py +4 -0
- components/versioning.py +103 -0
- saas_framework-0.1.1.dist-info/METADATA +450 -0
- saas_framework-0.1.1.dist-info/RECORD +82 -0
- saas_framework-0.1.1.dist-info/WHEEL +5 -0
- saas_framework-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.2"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_ProgressRing(
|
|
8
|
+
value: float | None = None,
|
|
9
|
+
size: int = 40,
|
|
10
|
+
stroke_width: float = 4,
|
|
11
|
+
color: str | None = None,
|
|
12
|
+
bgcolor: str | None = None,
|
|
13
|
+
label: str = "",
|
|
14
|
+
) -> ft.Stack:
|
|
15
|
+
"""Circular progress indicator with optional center label."""
|
|
16
|
+
ring = ft.ProgressRing(
|
|
17
|
+
value=value,
|
|
18
|
+
width=size,
|
|
19
|
+
height=size,
|
|
20
|
+
stroke_width=stroke_width,
|
|
21
|
+
color=color or Colors.ACCENT,
|
|
22
|
+
bgcolor=bgcolor or Colors.BG_ELEVATED,
|
|
23
|
+
stroke_align=-1,
|
|
24
|
+
year_2023=True,
|
|
25
|
+
)
|
|
26
|
+
label_text = ft.Container(
|
|
27
|
+
content=ft.Text(
|
|
28
|
+
label,
|
|
29
|
+
size=max(9, size // 5),
|
|
30
|
+
color=Colors.TEXT_PRIMARY,
|
|
31
|
+
weight=ft.FontWeight.W_600,
|
|
32
|
+
text_align=ft.TextAlign.CENTER,
|
|
33
|
+
),
|
|
34
|
+
width=size,
|
|
35
|
+
height=size,
|
|
36
|
+
alignment=ft.Alignment(0, 0),
|
|
37
|
+
)
|
|
38
|
+
stack = ft.Stack(
|
|
39
|
+
[ring] if not label else [ring, label_text],
|
|
40
|
+
width=size,
|
|
41
|
+
height=size,
|
|
42
|
+
clip_behavior=ft.ClipBehavior.HARD_EDGE,
|
|
43
|
+
alignment=ft.Alignment(0, 0),
|
|
44
|
+
)
|
|
45
|
+
return ft.Container(
|
|
46
|
+
content=stack,
|
|
47
|
+
width=size,
|
|
48
|
+
height=size,
|
|
49
|
+
alignment=ft.Alignment(0, 0),
|
|
50
|
+
padding=ft.Padding.all(0),
|
|
51
|
+
clip_behavior=ft.ClipBehavior.HARD_EDGE,
|
|
52
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _shimmer_block(width: int | str, height: int, radius: int = 6) -> ft.Container:
|
|
8
|
+
"""Single animated placeholder block."""
|
|
9
|
+
return ft.Container(
|
|
10
|
+
width=width if isinstance(width, int) else None,
|
|
11
|
+
expand=width == "expand",
|
|
12
|
+
height=height,
|
|
13
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
14
|
+
border_radius=radius,
|
|
15
|
+
animate=ft.Animation(1200, ft.AnimationCurve.EASE_IN_OUT),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def DEx_ShimmerCard(width: int = 280) -> ft.Container:
|
|
20
|
+
"""Card-shaped shimmer skeleton for loading states."""
|
|
21
|
+
return ft.Container(
|
|
22
|
+
content=ft.Column([
|
|
23
|
+
ft.Row([
|
|
24
|
+
_shimmer_block(40, 40, radius=20),
|
|
25
|
+
ft.Container(width=Spacing.SM),
|
|
26
|
+
ft.Column([
|
|
27
|
+
_shimmer_block(120, 12),
|
|
28
|
+
ft.Container(height=4),
|
|
29
|
+
_shimmer_block(80, 10),
|
|
30
|
+
ft.Container(height=6),
|
|
31
|
+
_shimmer_block(56, 8),
|
|
32
|
+
], spacing=0, tight=True),
|
|
33
|
+
ft.Container(expand=True),
|
|
34
|
+
_shimmer_block(64, 24, radius=12),
|
|
35
|
+
]),
|
|
36
|
+
ft.Container(height=Spacing.SM),
|
|
37
|
+
_shimmer_block("expand", 10),
|
|
38
|
+
ft.Container(height=4),
|
|
39
|
+
_shimmer_block("expand", 10),
|
|
40
|
+
ft.Container(height=4),
|
|
41
|
+
_shimmer_block(100, 10),
|
|
42
|
+
ft.Container(height=Spacing.SM),
|
|
43
|
+
ft.Row([
|
|
44
|
+
_shimmer_block(72, 20, radius=10),
|
|
45
|
+
ft.Container(width=Spacing.SM),
|
|
46
|
+
_shimmer_block(48, 20, radius=10),
|
|
47
|
+
ft.Container(width=Spacing.SM),
|
|
48
|
+
_shimmer_block(88, 20, radius=10),
|
|
49
|
+
], wrap=True),
|
|
50
|
+
], spacing=0, tight=True),
|
|
51
|
+
border_radius=Spacing.CARD_BORDER_RADIUS,
|
|
52
|
+
padding=Spacing.MD,
|
|
53
|
+
width=width,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def DEx_ShimmerListItem() -> ft.Container:
|
|
58
|
+
"""List item-shaped shimmer skeleton."""
|
|
59
|
+
return ft.Container(
|
|
60
|
+
content=ft.Row([
|
|
61
|
+
_shimmer_block(36, 36, radius=18),
|
|
62
|
+
ft.Container(width=Spacing.SM),
|
|
63
|
+
ft.Column([
|
|
64
|
+
_shimmer_block(140, 12),
|
|
65
|
+
ft.Container(height=4),
|
|
66
|
+
_shimmer_block(100, 10),
|
|
67
|
+
ft.Container(height=4),
|
|
68
|
+
_shimmer_block(72, 8),
|
|
69
|
+
], spacing=0, tight=True),
|
|
70
|
+
ft.Container(expand=True),
|
|
71
|
+
_shimmer_block(44, 18, radius=9),
|
|
72
|
+
]),
|
|
73
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.SM, vertical=Spacing.SM),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def DEx_ShimmerList(count: int = 3) -> ft.Column:
|
|
78
|
+
"""Vertical list of shimmer list items."""
|
|
79
|
+
items: list[ft.Control] = []
|
|
80
|
+
for i in range(count):
|
|
81
|
+
items.append(DEx_ShimmerListItem())
|
|
82
|
+
if i < count - 1:
|
|
83
|
+
items.append(ft.Divider(height=1, color=Colors.BORDER))
|
|
84
|
+
return ft.Column(items, spacing=0, tight=True)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_Tooltip(
|
|
8
|
+
message: str,
|
|
9
|
+
content: ft.Control,
|
|
10
|
+
wait_duration: int = 300,
|
|
11
|
+
) -> ft.Control:
|
|
12
|
+
"""Aplica tooltip estilizado ao controle e o retorna."""
|
|
13
|
+
content.tooltip = ft.Tooltip(
|
|
14
|
+
message=message,
|
|
15
|
+
wait_duration=wait_duration,
|
|
16
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
17
|
+
text_style=ft.TextStyle(size=12, color=Colors.TEXT_PRIMARY),
|
|
18
|
+
decoration=ft.BoxDecoration(
|
|
19
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
20
|
+
border_radius=ft.BorderRadius.all(Spacing.SM),
|
|
21
|
+
),
|
|
22
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.SM, vertical=Spacing.XS),
|
|
23
|
+
)
|
|
24
|
+
return content
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_AutoComplete(
|
|
8
|
+
suggestions: list[str],
|
|
9
|
+
label: str = "",
|
|
10
|
+
hint: str = "",
|
|
11
|
+
on_select=None,
|
|
12
|
+
) -> ft.AutoComplete:
|
|
13
|
+
"""Text field with dropdown suggestion list from a predefined set of values."""
|
|
14
|
+
suggestions_view = ft.ListView(
|
|
15
|
+
controls=[
|
|
16
|
+
ft.ListTile(
|
|
17
|
+
title=ft.Text(s, size=13, color=Colors.TEXT_PRIMARY),
|
|
18
|
+
bgcolor=Colors.BG_SURFACE,
|
|
19
|
+
hover_color=Colors.BG_ELEVATED,
|
|
20
|
+
)
|
|
21
|
+
for s in suggestions
|
|
22
|
+
],
|
|
23
|
+
padding=0,
|
|
24
|
+
)
|
|
25
|
+
return ft.AutoComplete(
|
|
26
|
+
suggestions=[ft.AutoCompleteSuggestion(key=s, value=s) for s in suggestions],
|
|
27
|
+
on_select=on_select,
|
|
28
|
+
suggestions_max_height=200,
|
|
29
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_Checkbox(
|
|
8
|
+
label: str = "",
|
|
9
|
+
value: bool = False,
|
|
10
|
+
on_change=None,
|
|
11
|
+
tristate: bool = False,
|
|
12
|
+
) -> ft.Checkbox:
|
|
13
|
+
"""Styled checkbox for boolean or tristate selection."""
|
|
14
|
+
return ft.Checkbox(
|
|
15
|
+
label=label,
|
|
16
|
+
value=value,
|
|
17
|
+
on_change=on_change,
|
|
18
|
+
tristate=tristate,
|
|
19
|
+
active_color=Colors.ACCENT,
|
|
20
|
+
check_color=Colors.TEXT_PRIMARY,
|
|
21
|
+
border_side=ft.BorderSide(1.5, Colors.BORDER),
|
|
22
|
+
label_style=ft.TextStyle(size=13, color=Colors.TEXT_PRIMARY),
|
|
23
|
+
fill_color={
|
|
24
|
+
ft.ControlState.SELECTED: Colors.ACCENT,
|
|
25
|
+
ft.ControlState.DEFAULT: ft.Colors.TRANSPARENT,
|
|
26
|
+
},
|
|
27
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_Chip(
|
|
8
|
+
label: str,
|
|
9
|
+
selected: bool = False,
|
|
10
|
+
on_select=None,
|
|
11
|
+
on_delete=None,
|
|
12
|
+
leading: ft.Control | None = None,
|
|
13
|
+
disabled: bool = False,
|
|
14
|
+
) -> ft.Chip:
|
|
15
|
+
"""Selectable tag chip for filters, categories, and multi-select inputs."""
|
|
16
|
+
return ft.Chip(
|
|
17
|
+
label=ft.Text(
|
|
18
|
+
label,
|
|
19
|
+
size=12,
|
|
20
|
+
color=Colors.TEXT_PRIMARY if selected else Colors.TEXT_SECONDARY,
|
|
21
|
+
),
|
|
22
|
+
selected=selected,
|
|
23
|
+
on_select=on_select,
|
|
24
|
+
on_delete=on_delete,
|
|
25
|
+
leading=leading,
|
|
26
|
+
disabled=disabled,
|
|
27
|
+
selected_color=Colors.ACCENT,
|
|
28
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
29
|
+
check_color=Colors.TEXT_PRIMARY,
|
|
30
|
+
border_side=ft.BorderSide(1, Colors.ACCENT if selected else Colors.BORDER),
|
|
31
|
+
shape=ft.RoundedRectangleBorder(radius=20),
|
|
32
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.SM, vertical=Spacing.XS),
|
|
33
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_DateField(
|
|
8
|
+
label: str = "Data",
|
|
9
|
+
value: str = "",
|
|
10
|
+
hint: str = "dd/mm/aaaa",
|
|
11
|
+
on_change=None,
|
|
12
|
+
width: int | None = None,
|
|
13
|
+
) -> ft.TextField:
|
|
14
|
+
"""Date field that opens a native calendar picker on icon click."""
|
|
15
|
+
picker = ft.DatePicker()
|
|
16
|
+
|
|
17
|
+
def _on_picked(e):
|
|
18
|
+
if e.control.value:
|
|
19
|
+
field.value = e.control.value.strftime("%d/%m/%Y")
|
|
20
|
+
try:
|
|
21
|
+
field.update()
|
|
22
|
+
except RuntimeError:
|
|
23
|
+
pass
|
|
24
|
+
if on_change:
|
|
25
|
+
on_change(e)
|
|
26
|
+
|
|
27
|
+
picker.on_change = _on_picked
|
|
28
|
+
|
|
29
|
+
def _open(e):
|
|
30
|
+
if not hasattr(e.page, "overlay"):
|
|
31
|
+
return
|
|
32
|
+
if picker not in e.page.overlay:
|
|
33
|
+
e.page.overlay.append(picker)
|
|
34
|
+
e.page.update()
|
|
35
|
+
picker.open = True
|
|
36
|
+
e.page.update()
|
|
37
|
+
|
|
38
|
+
field = ft.TextField(
|
|
39
|
+
label=label,
|
|
40
|
+
value=value,
|
|
41
|
+
hint_text=hint,
|
|
42
|
+
read_only=True,
|
|
43
|
+
suffix=ft.IconButton(
|
|
44
|
+
ft.Icons.CALENDAR_MONTH_ROUNDED,
|
|
45
|
+
on_click=_open,
|
|
46
|
+
icon_color=Colors.TEXT_SECONDARY,
|
|
47
|
+
icon_size=18,
|
|
48
|
+
),
|
|
49
|
+
border_color=Colors.BORDER,
|
|
50
|
+
focused_border_color=Colors.ACCENT,
|
|
51
|
+
label_style=ft.TextStyle(color=Colors.TEXT_SECONDARY),
|
|
52
|
+
text_style=ft.TextStyle(color=Colors.TEXT_PRIMARY),
|
|
53
|
+
hint_style=ft.TextStyle(color=Colors.TEXT_MUTED),
|
|
54
|
+
cursor_color=Colors.ACCENT,
|
|
55
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
56
|
+
width=width,
|
|
57
|
+
)
|
|
58
|
+
return field
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def DEx_DateRangeField(
|
|
62
|
+
start_label: str = "Data de início",
|
|
63
|
+
end_label: str = "Data de término",
|
|
64
|
+
on_change=None,
|
|
65
|
+
width: int | None = None,
|
|
66
|
+
) -> ft.Column:
|
|
67
|
+
"""Two date fields stacked vertically for date range input."""
|
|
68
|
+
return ft.Column([
|
|
69
|
+
DEx_DateField(label=start_label, on_change=on_change, width=width),
|
|
70
|
+
ft.Container(height=Spacing.SM),
|
|
71
|
+
DEx_DateField(label=end_label, on_change=on_change, width=width),
|
|
72
|
+
], spacing=0, tight=True)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_Dropdown(
|
|
8
|
+
label: str,
|
|
9
|
+
options: list[str],
|
|
10
|
+
on_change=None,
|
|
11
|
+
) -> ft.Dropdown:
|
|
12
|
+
"""Create the versioned styled dropdown selector."""
|
|
13
|
+
dd = ft.Dropdown(
|
|
14
|
+
label=label,
|
|
15
|
+
options=[ft.dropdown.Option(o) for o in options],
|
|
16
|
+
border_color=Colors.BORDER,
|
|
17
|
+
focused_border_color=Colors.ACCENT,
|
|
18
|
+
label_style=ft.TextStyle(color=Colors.TEXT_SECONDARY, size=12),
|
|
19
|
+
text_style=ft.TextStyle(color=Colors.TEXT_PRIMARY, size=14),
|
|
20
|
+
bgcolor=Colors.BG_SURFACE,
|
|
21
|
+
border_radius=Spacing.BUTTON_BORDER_RADIUS,
|
|
22
|
+
)
|
|
23
|
+
if on_change is not None:
|
|
24
|
+
dd.on_change = on_change
|
|
25
|
+
return dd
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.1.0"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_FilePicker(
|
|
8
|
+
on_result=None,
|
|
9
|
+
on_upload=None,
|
|
10
|
+
allowed_extensions: list[str] | None = None,
|
|
11
|
+
allow_multiple: bool = False,
|
|
12
|
+
) -> ft.FilePicker:
|
|
13
|
+
"""File picker overlay. Must be added to page.overlay before use."""
|
|
14
|
+
picker = ft.FilePicker()
|
|
15
|
+
if on_result:
|
|
16
|
+
picker.on_result = on_result
|
|
17
|
+
if on_upload:
|
|
18
|
+
picker.on_upload = on_upload
|
|
19
|
+
return picker
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def DEx_FilePickerButton(
|
|
23
|
+
label: str = "Selecionar arquivo",
|
|
24
|
+
icon: str = ft.Icons.UPLOAD_FILE_ROUNDED,
|
|
25
|
+
allowed_extensions: list[str] | None = None,
|
|
26
|
+
allow_multiple: bool = False,
|
|
27
|
+
on_result=None,
|
|
28
|
+
) -> ft.Column:
|
|
29
|
+
"""Self-contained file picker button. Add col.data (FilePicker) to page.overlay."""
|
|
30
|
+
result_text = ft.Text("", size=12, color=Colors.TEXT_MUTED)
|
|
31
|
+
|
|
32
|
+
picker = ft.FilePicker()
|
|
33
|
+
|
|
34
|
+
def _on_result(e):
|
|
35
|
+
if e.files:
|
|
36
|
+
result_text.value = ", ".join(f.name for f in e.files)
|
|
37
|
+
else:
|
|
38
|
+
result_text.value = "Nenhum arquivo selecionado"
|
|
39
|
+
try:
|
|
40
|
+
result_text.update()
|
|
41
|
+
except RuntimeError:
|
|
42
|
+
pass
|
|
43
|
+
if on_result:
|
|
44
|
+
on_result(e)
|
|
45
|
+
|
|
46
|
+
picker.on_result = _on_result
|
|
47
|
+
|
|
48
|
+
async def open_picker(e):
|
|
49
|
+
if hasattr(e.page, "overlay") and picker not in e.page.overlay:
|
|
50
|
+
e.page.overlay.append(picker)
|
|
51
|
+
e.page.update()
|
|
52
|
+
await picker.pick_files(
|
|
53
|
+
allow_multiple=allow_multiple,
|
|
54
|
+
allowed_extensions=allowed_extensions,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
btn = ft.Button(
|
|
58
|
+
content=label,
|
|
59
|
+
icon=icon,
|
|
60
|
+
on_click=open_picker,
|
|
61
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
62
|
+
color=Colors.TEXT_PRIMARY,
|
|
63
|
+
style=ft.ButtonStyle(
|
|
64
|
+
shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
col = ft.Column([btn, result_text], spacing=Spacing.XS, tight=True)
|
|
69
|
+
col.data = picker
|
|
70
|
+
return col
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_Radio(label: str, value: str) -> ft.Radio:
|
|
8
|
+
"""Single radio option — always used inside DEx_RadioGroup."""
|
|
9
|
+
return ft.Radio(
|
|
10
|
+
label=label,
|
|
11
|
+
value=value,
|
|
12
|
+
active_color=Colors.ACCENT,
|
|
13
|
+
fill_color={
|
|
14
|
+
ft.ControlState.SELECTED: Colors.ACCENT,
|
|
15
|
+
ft.ControlState.DEFAULT: Colors.BORDER,
|
|
16
|
+
},
|
|
17
|
+
label_style=ft.TextStyle(size=13, color=Colors.TEXT_PRIMARY),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def DEx_RadioGroup(
|
|
22
|
+
options: list[dict],
|
|
23
|
+
value: str | None = None,
|
|
24
|
+
on_change=None,
|
|
25
|
+
horizontal: bool = False,
|
|
26
|
+
) -> ft.RadioGroup:
|
|
27
|
+
"""Styled radio group for mutually exclusive option selection.
|
|
28
|
+
|
|
29
|
+
Each entry in options: {"value": str, "label": str}
|
|
30
|
+
"""
|
|
31
|
+
radios = [DEx_Radio(label=o["label"], value=o["value"]) for o in options]
|
|
32
|
+
content = (
|
|
33
|
+
ft.Row(radios, spacing=Spacing.MD, wrap=True)
|
|
34
|
+
if horizontal
|
|
35
|
+
else ft.Column(radios, spacing=Spacing.XS, tight=True)
|
|
36
|
+
)
|
|
37
|
+
return ft.RadioGroup(content=content, value=value, on_change=on_change)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_SearchBar(
|
|
8
|
+
suggestions: list[str] | None = None,
|
|
9
|
+
hint: str = "Pesquisar...",
|
|
10
|
+
bar_hint: str = "Pesquisar...",
|
|
11
|
+
on_change=None,
|
|
12
|
+
on_submit=None,
|
|
13
|
+
width: float | None = None,
|
|
14
|
+
) -> ft.SearchBar:
|
|
15
|
+
"""Full SearchBar with expandable overlay and suggestion list."""
|
|
16
|
+
controls = [
|
|
17
|
+
ft.ListTile(
|
|
18
|
+
title=ft.Text(s, size=13, color=Colors.TEXT_PRIMARY),
|
|
19
|
+
on_click=lambda e, s=s: (
|
|
20
|
+
e.control.page.close(e.control.page.controls[-1]),
|
|
21
|
+
) if e.control.page else None,
|
|
22
|
+
)
|
|
23
|
+
for s in (suggestions or [])
|
|
24
|
+
]
|
|
25
|
+
return ft.SearchBar(
|
|
26
|
+
controls=controls,
|
|
27
|
+
view_hint_text=hint,
|
|
28
|
+
bar_hint_text=bar_hint,
|
|
29
|
+
bar_bgcolor=Colors.BG_ELEVATED,
|
|
30
|
+
bar_overlay_color=Colors.BG_SURFACE,
|
|
31
|
+
view_bgcolor=Colors.BG_SURFACE,
|
|
32
|
+
bar_border_side=ft.BorderSide(1, Colors.BORDER),
|
|
33
|
+
on_change=on_change,
|
|
34
|
+
on_submit=on_submit,
|
|
35
|
+
width=width,
|
|
36
|
+
bar_leading=ft.Icon(ft.Icons.SEARCH_ROUNDED, color=Colors.TEXT_MUTED, size=18),
|
|
37
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.inputs.DEx_TextField import DEx_TextField
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_SearchField(hint: str = "Buscar...", on_change=None) -> ft.TextField:
|
|
8
|
+
"""Create the versioned search field with the standard search icon."""
|
|
9
|
+
return DEx_TextField(label="", hint=hint, icon=ft.Icons.SEARCH, on_change=on_change)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_Slider(
|
|
8
|
+
min: float = 0,
|
|
9
|
+
max: float = 100,
|
|
10
|
+
value: float = 50,
|
|
11
|
+
divisions: int | None = None,
|
|
12
|
+
label: str | None = "{value}",
|
|
13
|
+
on_change=None,
|
|
14
|
+
) -> ft.Slider:
|
|
15
|
+
"""Styled single-value slider for numeric range input."""
|
|
16
|
+
return ft.Slider(
|
|
17
|
+
min=min,
|
|
18
|
+
max=max,
|
|
19
|
+
value=value,
|
|
20
|
+
divisions=divisions,
|
|
21
|
+
label=label,
|
|
22
|
+
on_change=on_change,
|
|
23
|
+
active_color=Colors.ACCENT,
|
|
24
|
+
inactive_color=Colors.BG_ELEVATED,
|
|
25
|
+
thumb_color=Colors.ACCENT,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def DEx_RangeSlider(
|
|
30
|
+
min: float = 0,
|
|
31
|
+
max: float = 100,
|
|
32
|
+
start_value: float = 20,
|
|
33
|
+
end_value: float = 80,
|
|
34
|
+
divisions: int | None = None,
|
|
35
|
+
label: str | None = "{value}",
|
|
36
|
+
on_change=None,
|
|
37
|
+
) -> ft.RangeSlider:
|
|
38
|
+
"""Styled two-handle slider for selecting a numeric range."""
|
|
39
|
+
return ft.RangeSlider(
|
|
40
|
+
min=min,
|
|
41
|
+
max=max,
|
|
42
|
+
start_value=start_value,
|
|
43
|
+
end_value=end_value,
|
|
44
|
+
divisions=divisions,
|
|
45
|
+
label=label,
|
|
46
|
+
on_change=on_change,
|
|
47
|
+
active_color=Colors.ACCENT,
|
|
48
|
+
inactive_color=Colors.BG_ELEVATED,
|
|
49
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_Switch(
|
|
8
|
+
label: str = "",
|
|
9
|
+
value: bool = False,
|
|
10
|
+
on_change=None,
|
|
11
|
+
label_position: ft.LabelPosition = ft.LabelPosition.RIGHT,
|
|
12
|
+
) -> ft.Switch:
|
|
13
|
+
"""Styled toggle switch for binary settings and feature flags."""
|
|
14
|
+
return ft.Switch(
|
|
15
|
+
label=label,
|
|
16
|
+
value=value,
|
|
17
|
+
on_change=on_change,
|
|
18
|
+
label_position=label_position,
|
|
19
|
+
active_color=Colors.ACCENT,
|
|
20
|
+
active_track_color=Colors.ACCENT_DARK,
|
|
21
|
+
label_text_style=ft.TextStyle(size=13, color=Colors.TEXT_PRIMARY),
|
|
22
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_TextField(
|
|
8
|
+
label: str,
|
|
9
|
+
hint: str = "",
|
|
10
|
+
icon=None,
|
|
11
|
+
password: bool = False,
|
|
12
|
+
on_change=None,
|
|
13
|
+
) -> ft.TextField:
|
|
14
|
+
"""Create the versioned DEx text field for forms and filters."""
|
|
15
|
+
return ft.TextField(
|
|
16
|
+
label=label,
|
|
17
|
+
hint_text=hint,
|
|
18
|
+
prefix_icon=icon,
|
|
19
|
+
password=password,
|
|
20
|
+
can_reveal_password=password,
|
|
21
|
+
on_change=on_change,
|
|
22
|
+
border_color=Colors.BORDER,
|
|
23
|
+
focused_border_color=Colors.ACCENT,
|
|
24
|
+
label_style=ft.TextStyle(color=Colors.TEXT_SECONDARY, size=12),
|
|
25
|
+
text_style=ft.TextStyle(color=Colors.TEXT_PRIMARY, size=14),
|
|
26
|
+
cursor_color=Colors.ACCENT,
|
|
27
|
+
bgcolor=Colors.BG_SURFACE,
|
|
28
|
+
border_radius=Spacing.BUTTON_BORDER_RADIUS,
|
|
29
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def DEx_TimeField(
|
|
8
|
+
label: str = "Horário",
|
|
9
|
+
value: str = "",
|
|
10
|
+
hint: str = "hh:mm",
|
|
11
|
+
on_change=None,
|
|
12
|
+
width: int | None = None,
|
|
13
|
+
) -> ft.TextField:
|
|
14
|
+
"""Time field that opens a native clock picker on icon click."""
|
|
15
|
+
picker = ft.TimePicker()
|
|
16
|
+
|
|
17
|
+
def _on_picked(e):
|
|
18
|
+
if e.control.value:
|
|
19
|
+
field.value = e.control.value.strftime("%H:%M")
|
|
20
|
+
try:
|
|
21
|
+
field.update()
|
|
22
|
+
except RuntimeError:
|
|
23
|
+
pass
|
|
24
|
+
if on_change:
|
|
25
|
+
on_change(e)
|
|
26
|
+
|
|
27
|
+
picker.on_change = _on_picked
|
|
28
|
+
|
|
29
|
+
def _open(e):
|
|
30
|
+
if not hasattr(e.page, "overlay"):
|
|
31
|
+
return
|
|
32
|
+
if picker not in e.page.overlay:
|
|
33
|
+
e.page.overlay.append(picker)
|
|
34
|
+
e.page.update()
|
|
35
|
+
picker.open = True
|
|
36
|
+
e.page.update()
|
|
37
|
+
|
|
38
|
+
field = ft.TextField(
|
|
39
|
+
label=label,
|
|
40
|
+
value=value,
|
|
41
|
+
hint_text=hint,
|
|
42
|
+
read_only=True,
|
|
43
|
+
suffix=ft.IconButton(
|
|
44
|
+
ft.Icons.ACCESS_TIME_ROUNDED,
|
|
45
|
+
on_click=_open,
|
|
46
|
+
icon_color=Colors.TEXT_SECONDARY,
|
|
47
|
+
icon_size=18,
|
|
48
|
+
),
|
|
49
|
+
border_color=Colors.BORDER,
|
|
50
|
+
focused_border_color=Colors.ACCENT,
|
|
51
|
+
label_style=ft.TextStyle(color=Colors.TEXT_SECONDARY),
|
|
52
|
+
text_style=ft.TextStyle(color=Colors.TEXT_PRIMARY),
|
|
53
|
+
hint_style=ft.TextStyle(color=Colors.TEXT_MUTED),
|
|
54
|
+
cursor_color=Colors.ACCENT,
|
|
55
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
56
|
+
width=width,
|
|
57
|
+
)
|
|
58
|
+
return field
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def DEx_TimeRangeField(
|
|
62
|
+
start_label: str = "Hora de início",
|
|
63
|
+
end_label: str = "Hora de término",
|
|
64
|
+
on_change=None,
|
|
65
|
+
width: int | None = None,
|
|
66
|
+
) -> ft.Row:
|
|
67
|
+
"""Two time fields side by side for time range input."""
|
|
68
|
+
return ft.Row([
|
|
69
|
+
DEx_TimeField(label=start_label, on_change=on_change, width=width),
|
|
70
|
+
DEx_TimeField(label=end_label, on_change=on_change, width=width),
|
|
71
|
+
], spacing=Spacing.SM)
|
|
File without changes
|