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,43 @@
|
|
|
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_AnimatedSwitcher(
|
|
8
|
+
content: ft.Control,
|
|
9
|
+
duration: int = 350,
|
|
10
|
+
reverse_duration: int | None = None,
|
|
11
|
+
switch_in_curve: ft.AnimationCurve = ft.AnimationCurve.EASE_IN_OUT,
|
|
12
|
+
switch_out_curve: ft.AnimationCurve = ft.AnimationCurve.EASE_IN_OUT,
|
|
13
|
+
transition: ft.AnimatedSwitcherTransition = ft.AnimatedSwitcherTransition.SCALE,
|
|
14
|
+
) -> ft.AnimatedSwitcher:
|
|
15
|
+
"""Troca de conteúdo com animação. Altere .content + chame update() para animar."""
|
|
16
|
+
return ft.AnimatedSwitcher(
|
|
17
|
+
content=content,
|
|
18
|
+
duration=duration,
|
|
19
|
+
reverse_duration=reverse_duration or duration,
|
|
20
|
+
switch_in_curve=switch_in_curve,
|
|
21
|
+
switch_out_curve=switch_out_curve,
|
|
22
|
+
transition=transition,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def DEx_AnimatedContainer(
|
|
27
|
+
content: ft.Control,
|
|
28
|
+
width: int | float | None = None,
|
|
29
|
+
height: int | float | None = None,
|
|
30
|
+
bgcolor: str | None = None,
|
|
31
|
+
border_radius: int = 0,
|
|
32
|
+
duration: int = 300,
|
|
33
|
+
curve: ft.AnimationCurve = ft.AnimationCurve.EASE_IN_OUT,
|
|
34
|
+
) -> ft.Container:
|
|
35
|
+
"""Container com transições animadas de tamanho, cor e geometria."""
|
|
36
|
+
return ft.Container(
|
|
37
|
+
content=content,
|
|
38
|
+
width=width,
|
|
39
|
+
height=height,
|
|
40
|
+
bgcolor=bgcolor,
|
|
41
|
+
border_radius=border_radius,
|
|
42
|
+
animate=ft.Animation(duration, curve),
|
|
43
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
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_AppBar(
|
|
8
|
+
title: str,
|
|
9
|
+
leading: ft.Control | None = None,
|
|
10
|
+
actions: list[ft.Control] | None = None,
|
|
11
|
+
center_title: bool = False,
|
|
12
|
+
subtitle: str = "",
|
|
13
|
+
) -> ft.AppBar:
|
|
14
|
+
"""Top application bar with title, optional leading widget, and action buttons."""
|
|
15
|
+
title_col = ft.Column(
|
|
16
|
+
[
|
|
17
|
+
ft.Text(title, size=15, weight=ft.FontWeight.W_600, color=Colors.TEXT_PRIMARY),
|
|
18
|
+
ft.Text(subtitle, size=11, color=Colors.TEXT_SECONDARY) if subtitle else ft.Container(),
|
|
19
|
+
],
|
|
20
|
+
spacing=1,
|
|
21
|
+
tight=True,
|
|
22
|
+
)
|
|
23
|
+
return ft.AppBar(
|
|
24
|
+
title=title_col,
|
|
25
|
+
leading=leading,
|
|
26
|
+
actions=actions or [],
|
|
27
|
+
center_title=center_title,
|
|
28
|
+
bgcolor=Colors.BG_SURFACE,
|
|
29
|
+
color=Colors.TEXT_PRIMARY,
|
|
30
|
+
elevation=0,
|
|
31
|
+
shadow_color=ft.Colors.TRANSPARENT,
|
|
32
|
+
toolbar_height=Spacing.HEADER_HEIGHT,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def DEx_BottomAppBar(
|
|
37
|
+
actions: list[ft.Control],
|
|
38
|
+
fab: ft.Control | None = None,
|
|
39
|
+
) -> ft.BottomAppBar:
|
|
40
|
+
"""Bottom app bar with action icons and optional FAB notch."""
|
|
41
|
+
return ft.BottomAppBar(
|
|
42
|
+
content=ft.Row(actions, spacing=Spacing.XS),
|
|
43
|
+
bgcolor=Colors.BG_SURFACE,
|
|
44
|
+
elevation=0,
|
|
45
|
+
shadow_color=ft.Colors.TRANSPARENT,
|
|
46
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.SM, vertical=Spacing.XS),
|
|
47
|
+
notch_margin=Spacing.SM,
|
|
48
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
from components.layout.DEx_Sidebar import DEx_Sidebar
|
|
4
|
+
from components.layout.DEx_Header import DEx_Header
|
|
5
|
+
|
|
6
|
+
COMPONENT_VERSION = "0.0.1"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DEx_AppShell(ft.Row):
|
|
10
|
+
"""Versioned application shell with sidebar, header, and content host."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
page: ft.Page,
|
|
15
|
+
title: str = "",
|
|
16
|
+
subtitle: str = "",
|
|
17
|
+
header_actions: list[ft.Control] | None = None,
|
|
18
|
+
):
|
|
19
|
+
self._page_ref = page
|
|
20
|
+
self.title = title
|
|
21
|
+
self.subtitle = subtitle
|
|
22
|
+
self.header_actions = header_actions or []
|
|
23
|
+
self._content = ft.Container(expand=True)
|
|
24
|
+
self._route = "/dashboard"
|
|
25
|
+
|
|
26
|
+
page.bgcolor = Colors.BG_DEEP
|
|
27
|
+
page.padding = 0
|
|
28
|
+
page.spacing = 0
|
|
29
|
+
|
|
30
|
+
super().__init__(
|
|
31
|
+
controls=self._build_controls(),
|
|
32
|
+
spacing=0,
|
|
33
|
+
expand=True,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def set_content(self, control: ft.Control, title: str = None, subtitle: str = None):
|
|
37
|
+
if title:
|
|
38
|
+
self.title = title
|
|
39
|
+
if subtitle:
|
|
40
|
+
self.subtitle = subtitle
|
|
41
|
+
self._content = ft.Container(
|
|
42
|
+
content=ft.Column(
|
|
43
|
+
[ft.Container(content=control, padding=ft.Padding.all(Spacing.LG))],
|
|
44
|
+
spacing=0,
|
|
45
|
+
expand=True,
|
|
46
|
+
horizontal_alignment=ft.CrossAxisAlignment.START,
|
|
47
|
+
),
|
|
48
|
+
expand=True,
|
|
49
|
+
bgcolor=Colors.BG_DEEP,
|
|
50
|
+
)
|
|
51
|
+
self.controls = self._build_controls()
|
|
52
|
+
try:
|
|
53
|
+
self.update()
|
|
54
|
+
except RuntimeError:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
def _on_navigate(self, route: str):
|
|
58
|
+
self._route = route
|
|
59
|
+
self.controls = self._build_controls()
|
|
60
|
+
self.update()
|
|
61
|
+
|
|
62
|
+
def _build_controls(self) -> list[ft.Control]:
|
|
63
|
+
sidebar = DEx_Sidebar(current_route=self._route, on_navigate=self._on_navigate)
|
|
64
|
+
header = DEx_Header(self.title, self.subtitle, actions=self.header_actions)
|
|
65
|
+
|
|
66
|
+
main_area = ft.Column(
|
|
67
|
+
[
|
|
68
|
+
ft.Row([header], spacing=0, expand=True),
|
|
69
|
+
ft.Container(
|
|
70
|
+
content=self._content,
|
|
71
|
+
expand=True,
|
|
72
|
+
clip_behavior=ft.ClipBehavior.HARD_EDGE,
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
spacing=0,
|
|
76
|
+
expand=True,
|
|
77
|
+
alignment=ft.MainAxisAlignment.START,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return [sidebar, main_area]
|
|
@@ -0,0 +1,67 @@
|
|
|
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_Draggable(
|
|
8
|
+
content: ft.Control,
|
|
9
|
+
group: str = "default",
|
|
10
|
+
data=None,
|
|
11
|
+
content_when_dragging: ft.Control | None = None,
|
|
12
|
+
content_feedback: ft.Control | None = None,
|
|
13
|
+
) -> ft.Draggable:
|
|
14
|
+
"""Controle arrastável. Combine com DEx_DragTarget do mesmo grupo."""
|
|
15
|
+
placeholder = content_when_dragging or ft.Container(
|
|
16
|
+
width=60,
|
|
17
|
+
height=60,
|
|
18
|
+
border=ft.Border.all(1, Colors.BORDER),
|
|
19
|
+
border_radius=Spacing.CARD_BORDER_RADIUS,
|
|
20
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
21
|
+
)
|
|
22
|
+
feedback = content_feedback or ft.Container(
|
|
23
|
+
content=content,
|
|
24
|
+
opacity=0.7,
|
|
25
|
+
)
|
|
26
|
+
return ft.Draggable(
|
|
27
|
+
group=group,
|
|
28
|
+
content=content,
|
|
29
|
+
content_when_dragging=placeholder,
|
|
30
|
+
content_feedback=feedback,
|
|
31
|
+
data=data,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def DEx_DragTarget(
|
|
36
|
+
group: str = "default",
|
|
37
|
+
on_accept=None,
|
|
38
|
+
on_will_accept=None,
|
|
39
|
+
on_leave=None,
|
|
40
|
+
content: ft.Control | None = None,
|
|
41
|
+
content_when_empty: ft.Control | None = None,
|
|
42
|
+
) -> ft.DragTarget:
|
|
43
|
+
"""Alvo de soltura para DEx_Draggable do mesmo grupo."""
|
|
44
|
+
body = content or content_when_empty or ft.Container(
|
|
45
|
+
width=120,
|
|
46
|
+
height=80,
|
|
47
|
+
border=ft.Border.all(1, Colors.BORDER),
|
|
48
|
+
border_radius=Spacing.CARD_BORDER_RADIUS,
|
|
49
|
+
bgcolor=Colors.BG_SURFACE,
|
|
50
|
+
content=ft.Column(
|
|
51
|
+
[
|
|
52
|
+
ft.Icon(ft.Icons.UPLOAD_ROUNDED, size=20, color=Colors.TEXT_MUTED),
|
|
53
|
+
ft.Text("Solte aqui", size=11, color=Colors.TEXT_MUTED),
|
|
54
|
+
],
|
|
55
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
56
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
57
|
+
spacing=4,
|
|
58
|
+
tight=True,
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
return ft.DragTarget(
|
|
62
|
+
group=group,
|
|
63
|
+
content=body,
|
|
64
|
+
on_accept=on_accept,
|
|
65
|
+
on_will_accept=on_will_accept,
|
|
66
|
+
on_leave=on_leave,
|
|
67
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
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_GestureDetector(
|
|
8
|
+
content: ft.Control,
|
|
9
|
+
on_tap=None,
|
|
10
|
+
on_double_tap=None,
|
|
11
|
+
on_long_press=None,
|
|
12
|
+
on_hover=None,
|
|
13
|
+
on_enter=None,
|
|
14
|
+
on_exit=None,
|
|
15
|
+
on_pan_update=None,
|
|
16
|
+
mouse_cursor: ft.MouseCursor = ft.MouseCursor.CLICK,
|
|
17
|
+
) -> ft.GestureDetector:
|
|
18
|
+
"""Detector de gestos: tap, double-tap, long-press, hover, enter/exit e pan."""
|
|
19
|
+
return ft.GestureDetector(
|
|
20
|
+
content=content,
|
|
21
|
+
on_tap=on_tap,
|
|
22
|
+
on_double_tap=on_double_tap,
|
|
23
|
+
on_long_press_start=on_long_press,
|
|
24
|
+
on_hover=on_hover,
|
|
25
|
+
on_enter=on_enter,
|
|
26
|
+
on_exit=on_exit,
|
|
27
|
+
on_pan_update=on_pan_update,
|
|
28
|
+
mouse_cursor=mouse_cursor,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def DEx_Hoverable(
|
|
33
|
+
content_normal: ft.Control,
|
|
34
|
+
content_hover: ft.Control,
|
|
35
|
+
) -> ft.GestureDetector:
|
|
36
|
+
"""Alterna entre dois controles ao entrar/sair com o mouse."""
|
|
37
|
+
container_ref: ft.Ref[ft.Container] = ft.Ref()
|
|
38
|
+
|
|
39
|
+
def _toggle(show_hover: bool):
|
|
40
|
+
if container_ref.current:
|
|
41
|
+
container_ref.current.content = content_hover if show_hover else content_normal
|
|
42
|
+
try:
|
|
43
|
+
container_ref.current.update()
|
|
44
|
+
except RuntimeError:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
return ft.GestureDetector(
|
|
48
|
+
content=ft.Container(ref=container_ref, content=content_normal),
|
|
49
|
+
on_enter=lambda e: _toggle(True),
|
|
50
|
+
on_exit=lambda e: _toggle(False),
|
|
51
|
+
mouse_cursor=ft.MouseCursor.CLICK,
|
|
52
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DEx_Header(ft.Container):
|
|
8
|
+
"""Versioned page header with title, subtitle, search, and actions."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, title: str, subtitle: str = "", actions: list[ft.Control] | None = None):
|
|
11
|
+
self.title = title
|
|
12
|
+
self.subtitle = subtitle
|
|
13
|
+
self.actions = actions or []
|
|
14
|
+
super().__init__(
|
|
15
|
+
content=self._build_content(),
|
|
16
|
+
height=Spacing.HEADER_HEIGHT,
|
|
17
|
+
bgcolor=Colors.BG_DEEP,
|
|
18
|
+
border=ft.Border.only(bottom=ft.BorderSide(1, Colors.BORDER)),
|
|
19
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.LG),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def _build_content(self) -> ft.Row:
|
|
23
|
+
title_block = ft.Column(
|
|
24
|
+
[
|
|
25
|
+
ft.Text(self.title, size=20, weight=ft.FontWeight.W_700, color=Colors.TEXT_PRIMARY),
|
|
26
|
+
ft.Text(self.subtitle, size=12, color=Colors.TEXT_SECONDARY) if self.subtitle else ft.Container(),
|
|
27
|
+
],
|
|
28
|
+
spacing=2, tight=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
search = ft.TextField(
|
|
32
|
+
hint_text="Buscar...",
|
|
33
|
+
prefix_icon=ft.Icons.SEARCH,
|
|
34
|
+
border_color=Colors.BORDER,
|
|
35
|
+
focused_border_color=Colors.ACCENT,
|
|
36
|
+
text_style=ft.TextStyle(color=Colors.TEXT_PRIMARY, size=13),
|
|
37
|
+
hint_style=ft.TextStyle(color=Colors.TEXT_MUTED, size=13),
|
|
38
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
39
|
+
border_radius=8,
|
|
40
|
+
height=40,
|
|
41
|
+
content_padding=ft.Padding.symmetric(horizontal=12, vertical=8),
|
|
42
|
+
width=280,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
actions = ft.Row(
|
|
46
|
+
[
|
|
47
|
+
ft.IconButton(ft.Icons.NOTIFICATIONS_NONE_ROUNDED, icon_color=Colors.TEXT_SECONDARY, icon_size=20),
|
|
48
|
+
*self.actions,
|
|
49
|
+
ft.IconButton(ft.Icons.HELP_OUTLINE_ROUNDED, icon_color=Colors.TEXT_SECONDARY, icon_size=20),
|
|
50
|
+
],
|
|
51
|
+
spacing=0,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return ft.Row(
|
|
55
|
+
[title_block, ft.Container(expand=True), search, actions],
|
|
56
|
+
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
|
57
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.1.0"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DEx_NavItem(ft.Row):
|
|
8
|
+
"""Versioned sidebar navigation item with the DEx active accent."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
icon,
|
|
13
|
+
label: str,
|
|
14
|
+
route: str,
|
|
15
|
+
active: bool = False,
|
|
16
|
+
on_click=None,
|
|
17
|
+
):
|
|
18
|
+
self.icon_ref = icon
|
|
19
|
+
self.label = label
|
|
20
|
+
self.route = route
|
|
21
|
+
self.active = active
|
|
22
|
+
self._on_click = on_click
|
|
23
|
+
super().__init__(
|
|
24
|
+
controls=[self._build_accent_line(), self._build_icon_label()],
|
|
25
|
+
spacing=0,
|
|
26
|
+
tight=True,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def _build_accent_line(self) -> ft.Container:
|
|
30
|
+
return ft.Container(
|
|
31
|
+
width=3,
|
|
32
|
+
height=36,
|
|
33
|
+
bgcolor=Colors.ACCENT if self.active else "transparent",
|
|
34
|
+
border_radius=ft.BorderRadius.only(top_right=3, bottom_right=3),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def _build_icon_label(self) -> ft.Container:
|
|
38
|
+
return ft.Container(
|
|
39
|
+
content=ft.Row(
|
|
40
|
+
[
|
|
41
|
+
ft.Icon(
|
|
42
|
+
self.icon_ref,
|
|
43
|
+
size=18,
|
|
44
|
+
color=Colors.ACCENT if self.active else Colors.TEXT_SECONDARY,
|
|
45
|
+
),
|
|
46
|
+
ft.Text(
|
|
47
|
+
self.label,
|
|
48
|
+
size=13,
|
|
49
|
+
weight=ft.FontWeight.W_600 if self.active else ft.FontWeight.NORMAL,
|
|
50
|
+
color=Colors.TEXT_PRIMARY if self.active else Colors.TEXT_SECONDARY,
|
|
51
|
+
),
|
|
52
|
+
],
|
|
53
|
+
spacing=Spacing.SM,
|
|
54
|
+
),
|
|
55
|
+
bgcolor=f"{Colors.ACCENT}18" if self.active else "transparent",
|
|
56
|
+
border_radius=ft.BorderRadius.only(top_right=8, bottom_right=8),
|
|
57
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD - 3, vertical=10),
|
|
58
|
+
expand=True,
|
|
59
|
+
on_click=lambda e: self._on_click(self.route) if self._on_click else None,
|
|
60
|
+
ink=True,
|
|
61
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
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_NavBarItem(label: str, icon: str, selected_icon: str | None = None) -> ft.NavigationBarDestination:
|
|
8
|
+
"""Single destination entry for DEx_NavigationBar."""
|
|
9
|
+
return ft.NavigationBarDestination(
|
|
10
|
+
label=label,
|
|
11
|
+
icon=icon,
|
|
12
|
+
selected_icon=selected_icon or icon,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def DEx_NavigationBar(
|
|
17
|
+
destinations: list[ft.NavigationBarDestination],
|
|
18
|
+
selected_index: int = 0,
|
|
19
|
+
on_change=None,
|
|
20
|
+
) -> ft.NavigationBar:
|
|
21
|
+
"""Bottom navigation bar for mobile-style primary navigation between 3–5 destinations."""
|
|
22
|
+
return ft.NavigationBar(
|
|
23
|
+
destinations=destinations,
|
|
24
|
+
selected_index=selected_index,
|
|
25
|
+
on_change=on_change,
|
|
26
|
+
bgcolor=Colors.BG_SURFACE,
|
|
27
|
+
indicator_color=Colors.ACCENT,
|
|
28
|
+
shadow_color=ft.Colors.TRANSPARENT,
|
|
29
|
+
label_behavior=ft.NavigationBarLabelBehavior.ALWAYS_SHOW,
|
|
30
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
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_DrawerItem(
|
|
8
|
+
label: str,
|
|
9
|
+
icon: str,
|
|
10
|
+
selected: bool = False,
|
|
11
|
+
on_click=None,
|
|
12
|
+
) -> ft.Container:
|
|
13
|
+
"""Single row item inside a DEx_NavigationDrawer."""
|
|
14
|
+
return ft.Container(
|
|
15
|
+
content=ft.Row(
|
|
16
|
+
[
|
|
17
|
+
ft.Icon(
|
|
18
|
+
icon,
|
|
19
|
+
size=18,
|
|
20
|
+
color=Colors.ACCENT if selected else Colors.TEXT_SECONDARY,
|
|
21
|
+
),
|
|
22
|
+
ft.Text(
|
|
23
|
+
label,
|
|
24
|
+
size=13,
|
|
25
|
+
color=Colors.ACCENT if selected else Colors.TEXT_PRIMARY,
|
|
26
|
+
weight=ft.FontWeight.W_600 if selected else ft.FontWeight.NORMAL,
|
|
27
|
+
),
|
|
28
|
+
],
|
|
29
|
+
spacing=Spacing.MD,
|
|
30
|
+
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
|
31
|
+
),
|
|
32
|
+
bgcolor=f"{Colors.ACCENT}22" if selected else ft.Colors.TRANSPARENT,
|
|
33
|
+
border_radius=Spacing.BUTTON_BORDER_RADIUS,
|
|
34
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=10),
|
|
35
|
+
on_click=on_click,
|
|
36
|
+
ink=True,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def DEx_NavigationDrawer(
|
|
41
|
+
items: list[ft.Control],
|
|
42
|
+
header: ft.Control | None = None,
|
|
43
|
+
on_dismiss=None,
|
|
44
|
+
) -> ft.NavigationDrawer:
|
|
45
|
+
"""Side sheet navigation drawer for secondary navigation or context panels."""
|
|
46
|
+
controls: list[ft.Control] = []
|
|
47
|
+
if header:
|
|
48
|
+
controls.append(header)
|
|
49
|
+
controls.append(ft.Container(height=1, bgcolor=Colors.BORDER))
|
|
50
|
+
controls.append(ft.Container(height=Spacing.SM))
|
|
51
|
+
controls.extend(items)
|
|
52
|
+
|
|
53
|
+
return ft.NavigationDrawer(
|
|
54
|
+
controls=controls,
|
|
55
|
+
on_dismiss=on_dismiss,
|
|
56
|
+
bgcolor=Colors.BG_SURFACE,
|
|
57
|
+
shadow_color=ft.Colors.TRANSPARENT,
|
|
58
|
+
indicator_color=f"{Colors.ACCENT}22",
|
|
59
|
+
indicator_shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
60
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
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_RailItem(
|
|
8
|
+
label: str,
|
|
9
|
+
icon: str,
|
|
10
|
+
selected_icon: str | None = None,
|
|
11
|
+
) -> ft.NavigationRailDestination:
|
|
12
|
+
"""Single destination entry for DEx_NavigationRail."""
|
|
13
|
+
return ft.NavigationRailDestination(
|
|
14
|
+
label=label,
|
|
15
|
+
icon=icon,
|
|
16
|
+
selected_icon=selected_icon or icon,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def DEx_NavigationRail(
|
|
21
|
+
destinations: list[ft.NavigationRailDestination],
|
|
22
|
+
selected_index: int = 0,
|
|
23
|
+
on_change=None,
|
|
24
|
+
extended: bool = False,
|
|
25
|
+
leading: ft.Control | None = None,
|
|
26
|
+
trailing: ft.Control | None = None,
|
|
27
|
+
) -> ft.NavigationRail:
|
|
28
|
+
"""Compact vertical rail for secondary or desktop navigation."""
|
|
29
|
+
return ft.NavigationRail(
|
|
30
|
+
destinations=destinations,
|
|
31
|
+
selected_index=selected_index,
|
|
32
|
+
on_change=on_change,
|
|
33
|
+
extended=extended,
|
|
34
|
+
leading=leading,
|
|
35
|
+
trailing=trailing,
|
|
36
|
+
bgcolor=Colors.BG_SURFACE,
|
|
37
|
+
indicator_color=Colors.ACCENT,
|
|
38
|
+
indicator_shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
39
|
+
min_width=56,
|
|
40
|
+
min_extended_width=200,
|
|
41
|
+
group_alignment=-1.0,
|
|
42
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
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_ResponsiveRow(
|
|
8
|
+
controls: list | None = None,
|
|
9
|
+
columns: int = 12,
|
|
10
|
+
spacing: int | None = None,
|
|
11
|
+
run_spacing: int | None = None,
|
|
12
|
+
vertical_alignment: ft.CrossAxisAlignment = ft.CrossAxisAlignment.START,
|
|
13
|
+
) -> ft.ResponsiveRow:
|
|
14
|
+
"""Grid responsivo de 12 colunas. Defina col= nos controles filhos."""
|
|
15
|
+
return ft.ResponsiveRow(
|
|
16
|
+
controls=controls or [],
|
|
17
|
+
columns=columns,
|
|
18
|
+
spacing=spacing if spacing is not None else Spacing.MD,
|
|
19
|
+
run_spacing=run_spacing if run_spacing is not None else Spacing.MD,
|
|
20
|
+
vertical_alignment=vertical_alignment,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def DEx_Col(
|
|
25
|
+
content: ft.Control,
|
|
26
|
+
xs: int = 12,
|
|
27
|
+
sm: int | None = None,
|
|
28
|
+
md: int | None = None,
|
|
29
|
+
lg: int | None = None,
|
|
30
|
+
xl: int | None = None,
|
|
31
|
+
) -> ft.Container:
|
|
32
|
+
"""Coluna responsiva para uso dentro de DEx_ResponsiveRow."""
|
|
33
|
+
col_spec: dict = {"xs": xs}
|
|
34
|
+
if sm is not None:
|
|
35
|
+
col_spec["sm"] = sm
|
|
36
|
+
if md is not None:
|
|
37
|
+
col_spec["md"] = md
|
|
38
|
+
if lg is not None:
|
|
39
|
+
col_spec["lg"] = lg
|
|
40
|
+
if xl is not None:
|
|
41
|
+
col_spec["xl"] = xl
|
|
42
|
+
return ft.Container(content=content, col=col_spec)
|