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,131 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
from components.layout.DEx_NavItem import DEx_NavItem
|
|
4
|
+
|
|
5
|
+
COMPONENT_VERSION = "0.0.1"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DEx_Sidebar(ft.Container):
|
|
9
|
+
"""Versioned DEx sidebar with grouped app navigation and user footer."""
|
|
10
|
+
|
|
11
|
+
NAV_ITEMS = [
|
|
12
|
+
(ft.Icons.DASHBOARD_ROUNDED, "Dashboard", "/dashboard"),
|
|
13
|
+
(ft.Icons.PEOPLE_ALT_ROUNDED, "Contatos", "/contacts"),
|
|
14
|
+
(ft.Icons.SHOW_CHART_ROUNDED, "Pipeline", "/pipeline"),
|
|
15
|
+
(ft.Icons.CONFIRMATION_NUMBER, "Negocios", "/deals"),
|
|
16
|
+
(ft.Icons.EMAIL_ROUNDED, "E-mail", "/email"),
|
|
17
|
+
(ft.Icons.CALENDAR_MONTH, "Agenda", "/calendar"),
|
|
18
|
+
(ft.Icons.ANALYTICS_ROUNDED, "Relatorios", "/reports"),
|
|
19
|
+
(ft.Icons.INTEGRATION_INSTRUCTIONS, "Integracoes", "/integrations"),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
def __init__(self, current_route: str = "/dashboard", on_navigate=None):
|
|
23
|
+
self.current_route = current_route
|
|
24
|
+
self.on_navigate = on_navigate
|
|
25
|
+
super().__init__(
|
|
26
|
+
content=self._build_content(),
|
|
27
|
+
width=Spacing.SIDEBAR_WIDTH,
|
|
28
|
+
bgcolor=Colors.BG_SURFACE,
|
|
29
|
+
border=ft.Border.only(right=ft.BorderSide(1, Colors.BORDER)),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def _navigate(self, route: str):
|
|
33
|
+
self.current_route = route
|
|
34
|
+
if self.on_navigate:
|
|
35
|
+
self.on_navigate(route)
|
|
36
|
+
self.content = self._build_content()
|
|
37
|
+
self.update()
|
|
38
|
+
|
|
39
|
+
def _build_content(self) -> ft.Column:
|
|
40
|
+
logo = ft.Container(
|
|
41
|
+
content=ft.Row(
|
|
42
|
+
[
|
|
43
|
+
ft.Container(
|
|
44
|
+
content=ft.Text("D", size=16, weight=ft.FontWeight.W_700, color=Colors.BG_DEEP),
|
|
45
|
+
width=32, height=32,
|
|
46
|
+
bgcolor=Colors.ACCENT,
|
|
47
|
+
border_radius=8,
|
|
48
|
+
alignment=ft.Alignment(0, 0),
|
|
49
|
+
),
|
|
50
|
+
ft.Column(
|
|
51
|
+
[
|
|
52
|
+
ft.Text("DEx", size=15, weight=ft.FontWeight.W_700, color=Colors.TEXT_PRIMARY),
|
|
53
|
+
ft.Text("Framework v4", size=10, color=Colors.TEXT_MUTED),
|
|
54
|
+
],
|
|
55
|
+
spacing=0, tight=True,
|
|
56
|
+
),
|
|
57
|
+
],
|
|
58
|
+
spacing=Spacing.SM,
|
|
59
|
+
),
|
|
60
|
+
padding=ft.Padding.only(left=Spacing.MD, top=Spacing.LG, bottom=Spacing.LG),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
nav_section = ft.Column(
|
|
64
|
+
controls=[
|
|
65
|
+
DEx_NavItem(icon, label, route, active=(route == self.current_route), on_click=self._navigate)
|
|
66
|
+
for icon, label, route in self.NAV_ITEMS
|
|
67
|
+
],
|
|
68
|
+
spacing=2,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
settings_nav = ft.Column(
|
|
72
|
+
[
|
|
73
|
+
DEx_NavItem(
|
|
74
|
+
ft.Icons.SETTINGS_ROUNDED, "Configuracoes", "/settings",
|
|
75
|
+
active=self.current_route == "/settings",
|
|
76
|
+
on_click=self._navigate,
|
|
77
|
+
),
|
|
78
|
+
],
|
|
79
|
+
spacing=2,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
user_footer = ft.Container(
|
|
83
|
+
content=ft.Row(
|
|
84
|
+
[
|
|
85
|
+
ft.Container(
|
|
86
|
+
content=ft.Text("AD", size=12, weight=ft.FontWeight.W_700, color=Colors.DATA),
|
|
87
|
+
width=34, height=34,
|
|
88
|
+
bgcolor=f"{Colors.DATA}22",
|
|
89
|
+
border_radius=17,
|
|
90
|
+
alignment=ft.Alignment(0, 0),
|
|
91
|
+
),
|
|
92
|
+
ft.Column(
|
|
93
|
+
[
|
|
94
|
+
ft.Text("Almir Duarte", size=13, weight=ft.FontWeight.W_600, color=Colors.TEXT_PRIMARY),
|
|
95
|
+
ft.Text("Admin", size=11, color=Colors.TEXT_MUTED),
|
|
96
|
+
],
|
|
97
|
+
spacing=0, tight=True, expand=True,
|
|
98
|
+
),
|
|
99
|
+
ft.IconButton(
|
|
100
|
+
icon=ft.Icons.LOGOUT_ROUNDED,
|
|
101
|
+
icon_size=16,
|
|
102
|
+
icon_color=Colors.TEXT_MUTED,
|
|
103
|
+
tooltip="Sair",
|
|
104
|
+
),
|
|
105
|
+
],
|
|
106
|
+
spacing=Spacing.SM,
|
|
107
|
+
),
|
|
108
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.MD),
|
|
109
|
+
border=ft.Border.only(top=ft.BorderSide(1, Colors.BORDER)),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return ft.Column(
|
|
113
|
+
[
|
|
114
|
+
logo,
|
|
115
|
+
ft.Container(
|
|
116
|
+
content=ft.Text(
|
|
117
|
+
"NAVEGACAO",
|
|
118
|
+
size=10,
|
|
119
|
+
color=Colors.TEXT_MUTED,
|
|
120
|
+
style=ft.TextStyle(letter_spacing=1.5),
|
|
121
|
+
),
|
|
122
|
+
padding=ft.Padding.only(left=Spacing.MD, bottom=Spacing.XS),
|
|
123
|
+
),
|
|
124
|
+
nav_section,
|
|
125
|
+
ft.Container(expand=True),
|
|
126
|
+
settings_nav,
|
|
127
|
+
user_footer,
|
|
128
|
+
],
|
|
129
|
+
spacing=0,
|
|
130
|
+
expand=True,
|
|
131
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
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_Stack(
|
|
8
|
+
controls: list | None = None,
|
|
9
|
+
width: int | float | None = None,
|
|
10
|
+
height: int | float | None = None,
|
|
11
|
+
expand: bool = False,
|
|
12
|
+
clip: bool = True,
|
|
13
|
+
) -> ft.Stack:
|
|
14
|
+
"""Stack com posicionamento absoluto. Use left/top/right/bottom nos filhos."""
|
|
15
|
+
return ft.Stack(
|
|
16
|
+
controls=controls or [],
|
|
17
|
+
width=width,
|
|
18
|
+
height=height,
|
|
19
|
+
expand=expand,
|
|
20
|
+
clip_behavior=ft.ClipBehavior.HARD_EDGE if clip else ft.ClipBehavior.NONE,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def DEx_StackOverlay(
|
|
25
|
+
base: ft.Control,
|
|
26
|
+
overlay: ft.Control,
|
|
27
|
+
overlay_alignment: ft.Alignment = ft.Alignment(1, -1),
|
|
28
|
+
width: int | float | None = None,
|
|
29
|
+
height: int | float | None = None,
|
|
30
|
+
) -> ft.Stack:
|
|
31
|
+
"""Stack com um controle base e um overlay posicionado por alinhamento."""
|
|
32
|
+
return ft.Stack(
|
|
33
|
+
controls=[
|
|
34
|
+
base,
|
|
35
|
+
ft.Container(
|
|
36
|
+
content=overlay,
|
|
37
|
+
alignment=overlay_alignment,
|
|
38
|
+
),
|
|
39
|
+
],
|
|
40
|
+
width=width,
|
|
41
|
+
height=height,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def DEx_StackBadge(
|
|
46
|
+
content: ft.Control,
|
|
47
|
+
badge_text: str,
|
|
48
|
+
badge_color: str | None = None,
|
|
49
|
+
size: int = 18,
|
|
50
|
+
) -> ft.Stack:
|
|
51
|
+
"""Controle com badge numérico no canto superior direito."""
|
|
52
|
+
color = badge_color or Colors.ACCENT
|
|
53
|
+
return ft.Stack(
|
|
54
|
+
controls=[
|
|
55
|
+
content,
|
|
56
|
+
ft.Container(
|
|
57
|
+
content=ft.Text(
|
|
58
|
+
badge_text,
|
|
59
|
+
size=9,
|
|
60
|
+
weight=ft.FontWeight.W_700,
|
|
61
|
+
color=Colors.BG_DEEP,
|
|
62
|
+
text_align=ft.TextAlign.CENTER,
|
|
63
|
+
),
|
|
64
|
+
width=size,
|
|
65
|
+
height=size,
|
|
66
|
+
border_radius=size // 2,
|
|
67
|
+
bgcolor=color,
|
|
68
|
+
alignment=ft.Alignment(0, 0),
|
|
69
|
+
right=0,
|
|
70
|
+
top=0,
|
|
71
|
+
),
|
|
72
|
+
],
|
|
73
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
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 _TabEntry:
|
|
8
|
+
def __init__(self, label: str, icon=None, content: ft.Control | None = None):
|
|
9
|
+
self.label = label
|
|
10
|
+
self.icon = icon
|
|
11
|
+
self.content = content
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def DEx_Tab(label: str, icon=None, content: ft.Control | None = None) -> _TabEntry:
|
|
15
|
+
"""Single tab definition for DEx_Tabs."""
|
|
16
|
+
return _TabEntry(label=label, icon=icon, content=content)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def DEx_Tabs(
|
|
20
|
+
tabs: list,
|
|
21
|
+
selected_index: int = 0,
|
|
22
|
+
on_change=None,
|
|
23
|
+
expand: bool = True,
|
|
24
|
+
) -> ft.Control:
|
|
25
|
+
"""Styled horizontal tab bar with underline indicator in Colors.ACCENT."""
|
|
26
|
+
if not tabs:
|
|
27
|
+
return ft.Container()
|
|
28
|
+
|
|
29
|
+
active = {"idx": selected_index}
|
|
30
|
+
content_ref: ft.Ref[ft.Container] = ft.Ref()
|
|
31
|
+
bar_ref: ft.Ref[ft.Row] = ft.Ref()
|
|
32
|
+
|
|
33
|
+
def _current_view():
|
|
34
|
+
tab = tabs[active["idx"]]
|
|
35
|
+
if isinstance(tab, _TabEntry) and tab.content is not None:
|
|
36
|
+
return tab.content
|
|
37
|
+
return ft.Container()
|
|
38
|
+
|
|
39
|
+
def _switch(idx: int):
|
|
40
|
+
def handler(e):
|
|
41
|
+
active["idx"] = idx
|
|
42
|
+
if content_ref.current:
|
|
43
|
+
content_ref.current.content = _current_view()
|
|
44
|
+
if bar_ref.current:
|
|
45
|
+
bar_ref.current.controls = _tab_buttons()
|
|
46
|
+
if on_change:
|
|
47
|
+
class _FakeEvent:
|
|
48
|
+
control = type("C", (), {"selected_index": idx})()
|
|
49
|
+
on_change(_FakeEvent())
|
|
50
|
+
try:
|
|
51
|
+
if content_ref.current:
|
|
52
|
+
content_ref.current.update()
|
|
53
|
+
if bar_ref.current:
|
|
54
|
+
bar_ref.current.update()
|
|
55
|
+
except RuntimeError:
|
|
56
|
+
pass
|
|
57
|
+
return handler
|
|
58
|
+
|
|
59
|
+
def _tab_btn(tab, idx: int) -> ft.Control:
|
|
60
|
+
label = tab.label if isinstance(tab, _TabEntry) else str(tab)
|
|
61
|
+
icon = tab.icon if isinstance(tab, _TabEntry) else None
|
|
62
|
+
is_active = active["idx"] == idx
|
|
63
|
+
|
|
64
|
+
row_items: list[ft.Control] = []
|
|
65
|
+
if icon:
|
|
66
|
+
row_items.append(ft.Icon(icon, size=14, color=Colors.ACCENT if is_active else Colors.TEXT_MUTED))
|
|
67
|
+
row_items.append(ft.Text(
|
|
68
|
+
label,
|
|
69
|
+
size=13,
|
|
70
|
+
weight=ft.FontWeight.W_600 if is_active else ft.FontWeight.W_400,
|
|
71
|
+
color=Colors.ACCENT if is_active else Colors.TEXT_SECONDARY,
|
|
72
|
+
))
|
|
73
|
+
|
|
74
|
+
return ft.Container(
|
|
75
|
+
content=ft.Row(row_items, spacing=4, tight=True),
|
|
76
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.SM),
|
|
77
|
+
border=ft.Border.only(
|
|
78
|
+
bottom=ft.BorderSide(2, Colors.ACCENT if is_active else ft.Colors.TRANSPARENT)
|
|
79
|
+
),
|
|
80
|
+
on_click=_switch(idx),
|
|
81
|
+
ink=True,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _tab_buttons() -> list[ft.Control]:
|
|
85
|
+
return [_tab_btn(tab, i) for i, tab in enumerate(tabs)]
|
|
86
|
+
|
|
87
|
+
tab_bar = ft.Container(
|
|
88
|
+
content=ft.Row(ref=bar_ref, controls=_tab_buttons(), spacing=0, scroll=ft.ScrollMode.AUTO),
|
|
89
|
+
border=ft.Border.only(bottom=ft.BorderSide(1, Colors.BORDER)),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
has_content = any(
|
|
93
|
+
isinstance(t, _TabEntry) and t.content is not None for t in tabs
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if has_content:
|
|
97
|
+
return ft.Column(
|
|
98
|
+
[
|
|
99
|
+
tab_bar,
|
|
100
|
+
ft.Container(ref=content_ref, content=_current_view(), expand=True),
|
|
101
|
+
],
|
|
102
|
+
spacing=0,
|
|
103
|
+
expand=expand,
|
|
104
|
+
)
|
|
105
|
+
return ft.Column(
|
|
106
|
+
[tab_bar],
|
|
107
|
+
spacing=0,
|
|
108
|
+
expand=expand,
|
|
109
|
+
tight=True,
|
|
110
|
+
)
|
|
File without changes
|
components/metadata.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Central catalog metadata for DEx Framework components."""
|
|
2
|
+
|
|
3
|
+
from components.versioning import ComponentMeta
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
COMPONENTS = [
|
|
7
|
+
ComponentMeta("DEx_Badge", "primitives", "0.0.1", "Rótulo compacto de status e categoria."),
|
|
8
|
+
ComponentMeta("DEx_Divider", "primitives", "0.0.1", "Separador horizontal sutil."),
|
|
9
|
+
ComponentMeta("DEx_SectionEyebrow", "primitives", "0.0.1", "Marcador de seção numerado em ciano."),
|
|
10
|
+
ComponentMeta("DEx_StatusDot", "primitives", "0.0.1", "Indicador de estado colorido em miniatura."),
|
|
11
|
+
ComponentMeta("DEx_Card", "cards", "0.0.1", "Superfície base para conteúdo agrupado."),
|
|
12
|
+
ComponentMeta("DEx_MetricCard", "cards", "0.0.1", "Card de KPI com valor, ícone e variação."),
|
|
13
|
+
ComponentMeta("DEx_ContactCard", "cards", "0.0.1", "Card de entidade com iniciais, metadados e rodapé."),
|
|
14
|
+
ComponentMeta("DEx_BtnPrimary", "buttons", "0.1.0", "Botão de ação primária."),
|
|
15
|
+
ComponentMeta("DEx_BtnGhost", "buttons", "0.0.1", "Botão de ação secundária com borda."),
|
|
16
|
+
ComponentMeta("DEx_BtnIcon", "buttons", "0.0.1", "Botão de ação somente com ícone."),
|
|
17
|
+
ComponentMeta("DEx_BtnFilled", "buttons", "0.0.1", "Botão preenchido de alta ênfase."),
|
|
18
|
+
ComponentMeta("DEx_BtnTonal", "buttons", "0.0.1", "Botão tonal de ênfase secundária."),
|
|
19
|
+
ComponentMeta("DEx_BtnText", "buttons", "0.0.1", "Botão textual de menor ênfase."),
|
|
20
|
+
ComponentMeta("DEx_BtnFAB", "buttons", "0.0.1", "Botão de ação flutuante para criação de registros."),
|
|
21
|
+
ComponentMeta("DEx_BtnSegmented", "buttons", "0.0.1", "Alternador de múltiplas opções mutuamente exclusivas."),
|
|
22
|
+
ComponentMeta("DEx_MenuBar", "buttons", "0.0.1", "Barra de menus horizontal com submenus."),
|
|
23
|
+
ComponentMeta("DEx_PopupMenu", "buttons", "0.0.1", "Menu de contexto acionado por ícone."),
|
|
24
|
+
ComponentMeta("DEx_TextField", "inputs", "0.0.1", "Campo de texto estilizado."),
|
|
25
|
+
ComponentMeta("DEx_SearchField", "inputs", "0.0.1", "Campo de busca com ícone de lupa."),
|
|
26
|
+
ComponentMeta("DEx_Dropdown", "inputs", "0.0.1", "Seletor de opções estilizado."),
|
|
27
|
+
ComponentMeta("DEx_Checkbox", "inputs", "0.0.1", "Caixa de seleção booleana ou tristate estilizada."),
|
|
28
|
+
ComponentMeta("DEx_Switch", "inputs", "0.0.1", "Toggle switch para configurações binárias."),
|
|
29
|
+
ComponentMeta("DEx_Radio", "inputs", "0.0.1", "Opção de rádio e grupo para seleção mutuamente exclusiva."),
|
|
30
|
+
ComponentMeta("DEx_Slider", "inputs", "0.0.1", "Slider de valor único e de intervalo para entrada numérica."),
|
|
31
|
+
ComponentMeta("DEx_Chip", "inputs", "0.0.1", "Chip selecionável para filtros e multi-seleção."),
|
|
32
|
+
ComponentMeta("DEx_AutoComplete", "inputs", "0.0.1", "Campo de texto com dropdown de sugestões predefinidas."),
|
|
33
|
+
ComponentMeta("DEx_SearchBar", "inputs", "0.0.1", "Barra de pesquisa expansível com overlay de sugestões."),
|
|
34
|
+
ComponentMeta("DEx_DataTable", "data", "0.0.1", "Tabela de dados estilizada."),
|
|
35
|
+
ComponentMeta("DEx_PipelineColumn", "data", "0.0.1", "Coluna de pipeline/Kanban por etapa."),
|
|
36
|
+
ComponentMeta("DEx_TreeView", "data", "0.0.1", "Árvore hierárquica com busca, expansão e seleção."),
|
|
37
|
+
ComponentMeta("DEx_AppShell", "layout", "0.0.1", "Shell da aplicação com sidebar e área de conteúdo."),
|
|
38
|
+
ComponentMeta("DEx_Sidebar", "layout", "0.0.1", "Sidebar de navegação vertical."),
|
|
39
|
+
ComponentMeta("DEx_Header", "layout", "0.0.1", "Cabeçalho de página com busca e ações."),
|
|
40
|
+
ComponentMeta("DEx_NavItem", "layout", "0.1.0", "Item de navegação com acento de estado ativo."),
|
|
41
|
+
ComponentMeta("DEx_Tabs", "layout", "0.0.1", "Barra de abas horizontal com indicador em ACCENT."),
|
|
42
|
+
ComponentMeta("DEx_NavigationBar", "layout", "0.0.1", "Barra de navegação inferior estilo mobile."),
|
|
43
|
+
ComponentMeta("DEx_NavigationRail", "layout", "0.0.1", "Rail de navegação vertical compacto para desktop."),
|
|
44
|
+
ComponentMeta("DEx_NavigationDrawer", "layout", "0.0.1", "Drawer lateral de navegação secundária."),
|
|
45
|
+
ComponentMeta("DEx_AppBar", "layout", "0.0.1", "Barra superior de aplicação com título e ações."),
|
|
46
|
+
ComponentMeta("DEx_BottomAppBar", "layout", "0.0.1", "Barra inferior com ações e suporte a FAB."),
|
|
47
|
+
ComponentMeta("DEx_Notify", "feedback", "0.0.1", "Notificação temporária tipo snackbar."),
|
|
48
|
+
ComponentMeta("DEx_AlertDialog", "feedback", "0.0.1", "Diálogo modal para confirmações e alertas."),
|
|
49
|
+
ComponentMeta("DEx_BottomSheet", "feedback", "0.0.2", "Painel deslizante que emerge da borda inferior."),
|
|
50
|
+
ComponentMeta("DEx_Banner", "feedback", "0.0.1", "Banner persistente abaixo do AppBar para avisos."),
|
|
51
|
+
ComponentMeta("DEx_Tooltip", "feedback", "0.0.1", "Dica contextual ao hover ou long-press."),
|
|
52
|
+
ComponentMeta("DEx_ListTile", "data", "0.0.1", "Item de lista com título, subtítulo, leading e trailing."),
|
|
53
|
+
ComponentMeta("DEx_ExpansionTile", "data", "0.0.1", "Tile expansível que revela controles filhos."),
|
|
54
|
+
ComponentMeta("DEx_ExpansionPanel", "data", "0.0.1", "Painel acordeão agrupado em ExpansionPanelList."),
|
|
55
|
+
ComponentMeta("DEx_ListView", "data", "0.0.1", "Lista vertical com scroll e divisores automáticos."),
|
|
56
|
+
ComponentMeta("DEx_GridView", "data", "0.0.1", "Grade responsiva para cards e itens repetidos."),
|
|
57
|
+
# Sprint 7 — Feedback e Status
|
|
58
|
+
ComponentMeta("DEx_ProgressBar", "feedback", "0.0.1", "Barra de progresso horizontal determinada ou indeterminada."),
|
|
59
|
+
ComponentMeta("DEx_ProgressRing", "feedback", "0.0.2", "Indicador circular de progresso com label opcional."),
|
|
60
|
+
ComponentMeta("DEx_Shimmer", "feedback", "0.0.1", "Esqueletos de carregamento: ShimmerCard, ShimmerList."),
|
|
61
|
+
# Sprint 8 — Mídia e Exibição
|
|
62
|
+
ComponentMeta("DEx_Avatar", "primitives", "0.0.1", "Avatar circular com iniciais coloridas e grupo com overflow."),
|
|
63
|
+
ComponentMeta("DEx_Image", "primitives", "0.0.1", "Imagem com bordas arredondadas e placeholder de estado vazio."),
|
|
64
|
+
ComponentMeta("DEx_Icon", "primitives", "0.0.1", "Ícone com tokens de tamanho SM/MD/LG/XL e badge overlay."),
|
|
65
|
+
ComponentMeta("DEx_Markdown", "primitives", "0.0.1", "Renderizador Markdown com suporte a tabelas e código."),
|
|
66
|
+
# Sprint 9 — Seletores Temporais e Arquivos
|
|
67
|
+
ComponentMeta("DEx_DateField", "inputs", "0.0.1", "Campo de data estilizado com ícone e range opcional."),
|
|
68
|
+
ComponentMeta("DEx_TimeField", "inputs", "0.0.1", "Campo de horário com máscara hh:mm e range opcional."),
|
|
69
|
+
ComponentMeta("DEx_FilePicker", "inputs", "0.1.0", "Seletor de arquivo nativo com filtro de extensões."),
|
|
70
|
+
# Sprint 10 — Layout Avançado
|
|
71
|
+
ComponentMeta("DEx_Stack", "layout", "0.0.1", "Stack com posicionamento absoluto, overlay e badge."),
|
|
72
|
+
ComponentMeta("DEx_ResponsiveRow", "layout", "0.0.1", "Grade responsiva de 12 colunas com DEx_Col."),
|
|
73
|
+
ComponentMeta("DEx_GestureDetector", "layout", "0.0.1", "Detector de tap, double-tap, long-press, hover e pan."),
|
|
74
|
+
ComponentMeta("DEx_Draggable", "layout", "0.0.1", "Drag-and-drop: Draggable + DragTarget por grupo."),
|
|
75
|
+
ComponentMeta("DEx_AnimatedSwitcher", "layout", "0.0.2", "Troca de conteúdo animada (FADE/SCALE/ROTATION)."),
|
|
76
|
+
ComponentMeta("DEx_Theme", "theme", "0.0.2", "Tokens de design e construtor de tema Flet."),
|
|
77
|
+
ComponentMeta("DEx_ColorThemes", "theme", "0.0.3", "Temas de cor acessíveis 60-30-10 para aplicações DEx."),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
GROUP_DESCRIPTIONS = {
|
|
82
|
+
"primitives": "Elementos visuais atômicos para composição de controles maiores.",
|
|
83
|
+
"cards": "Superfícies reutilizáveis para métricas, entidades e conteúdo agrupado.",
|
|
84
|
+
"buttons": "Controles de ação: primário, secundário e somente ícone.",
|
|
85
|
+
"inputs": "Controles de formulário e busca estilizados para o sistema visual DEx.",
|
|
86
|
+
"data": "Controles para tabelas, pipelines e visualizações de dados operacionais.",
|
|
87
|
+
"layout": "Frame da aplicação, navegação e controles de estrutura de página.",
|
|
88
|
+
"feedback": "Controles de feedback do usuário para estados transitórios.",
|
|
89
|
+
"theme": "Cores, espaçamento, tipografia e construção de tema compartilhados.",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_component_version(name: str) -> str:
|
|
94
|
+
"""Return a component version by name."""
|
|
95
|
+
for component in COMPONENTS:
|
|
96
|
+
if component.name == name:
|
|
97
|
+
return component.version
|
|
98
|
+
return "0.0.0"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
_PALETTE = [
|
|
7
|
+
Colors.ACCENT, Colors.DATA, Colors.SUCCESS,
|
|
8
|
+
Colors.WARNING, Colors.INFO, Colors.DANGER,
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _initials(name: str) -> str:
|
|
13
|
+
parts = name.strip().split()
|
|
14
|
+
if len(parts) >= 2:
|
|
15
|
+
return (parts[0][0] + parts[-1][0]).upper()
|
|
16
|
+
return name[:2].upper() if name else "?"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _color_for(name: str) -> str:
|
|
20
|
+
return _PALETTE[sum(ord(c) for c in name) % len(_PALETTE)]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def DEx_Avatar(
|
|
24
|
+
name: str = "",
|
|
25
|
+
src: str = "",
|
|
26
|
+
size: int = 36,
|
|
27
|
+
color: str | None = None,
|
|
28
|
+
on_click=None,
|
|
29
|
+
) -> ft.Container:
|
|
30
|
+
"""Circular avatar — image when src is provided, initials fallback otherwise."""
|
|
31
|
+
radius = size // 2
|
|
32
|
+
if src:
|
|
33
|
+
inner: ft.Control = ft.CircleAvatar(
|
|
34
|
+
foreground_image_src=src,
|
|
35
|
+
radius=radius,
|
|
36
|
+
)
|
|
37
|
+
else:
|
|
38
|
+
bg = color or _color_for(name)
|
|
39
|
+
inner = ft.CircleAvatar(
|
|
40
|
+
content=ft.Text(_initials(name), size=max(9, size // 3),
|
|
41
|
+
weight=ft.FontWeight.W_700, color=Colors.BG_DEEP),
|
|
42
|
+
bgcolor=bg,
|
|
43
|
+
radius=radius,
|
|
44
|
+
)
|
|
45
|
+
return ft.Container(
|
|
46
|
+
content=inner,
|
|
47
|
+
width=size,
|
|
48
|
+
height=size,
|
|
49
|
+
border_radius=radius,
|
|
50
|
+
clip_behavior=ft.ClipBehavior.HARD_EDGE,
|
|
51
|
+
on_click=on_click,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def DEx_AvatarGroup(
|
|
56
|
+
names: list[str],
|
|
57
|
+
size: int = 32,
|
|
58
|
+
max_visible: int = 4,
|
|
59
|
+
) -> ft.Stack:
|
|
60
|
+
"""Overlapping avatar group with overflow counter."""
|
|
61
|
+
visible = names[:max_visible]
|
|
62
|
+
overflow = len(names) - max_visible
|
|
63
|
+
overlap = size // 3
|
|
64
|
+
total_width = size + (len(visible) - 1) * (size - overlap)
|
|
65
|
+
if overflow > 0:
|
|
66
|
+
total_width += size - overlap
|
|
67
|
+
|
|
68
|
+
controls: list[ft.Control] = []
|
|
69
|
+
for i, name in enumerate(visible):
|
|
70
|
+
controls.append(ft.Container(
|
|
71
|
+
content=DEx_Avatar(name=name, size=size),
|
|
72
|
+
left=i * (size - overlap),
|
|
73
|
+
top=0,
|
|
74
|
+
))
|
|
75
|
+
if overflow > 0:
|
|
76
|
+
controls.append(ft.Container(
|
|
77
|
+
content=ft.CircleAvatar(
|
|
78
|
+
content=ft.Text(f"+{overflow}", size=max(8, size // 4),
|
|
79
|
+
weight=ft.FontWeight.W_600, color=Colors.TEXT_SECONDARY),
|
|
80
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
81
|
+
radius=size // 2,
|
|
82
|
+
),
|
|
83
|
+
width=size, height=size,
|
|
84
|
+
left=len(visible) * (size - overlap),
|
|
85
|
+
top=0,
|
|
86
|
+
))
|
|
87
|
+
return ft.Stack(controls, width=total_width, height=size)
|
|
@@ -0,0 +1,18 @@
|
|
|
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_Badge(
|
|
8
|
+
label: str,
|
|
9
|
+
color: str = Colors.DATA,
|
|
10
|
+
text_color: str = Colors.BG_DEEP,
|
|
11
|
+
) -> ft.Container:
|
|
12
|
+
"""Create the versioned compact badge for status and category labels."""
|
|
13
|
+
return ft.Container(
|
|
14
|
+
content=ft.Text(label, size=11, weight=ft.FontWeight.W_600, color=text_color),
|
|
15
|
+
bgcolor=color,
|
|
16
|
+
border_radius=4,
|
|
17
|
+
padding=ft.Padding.symmetric(horizontal=8, vertical=3),
|
|
18
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
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_Divider() -> ft.Container:
|
|
8
|
+
"""Create the versioned subtle horizontal divider."""
|
|
9
|
+
return ft.Container(
|
|
10
|
+
height=1,
|
|
11
|
+
bgcolor=Colors.BORDER,
|
|
12
|
+
margin=ft.Margin.symmetric(vertical=Spacing.SM),
|
|
13
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
# Size tokens
|
|
7
|
+
ICON_SM = 16
|
|
8
|
+
ICON_MD = 20
|
|
9
|
+
ICON_LG = 24
|
|
10
|
+
ICON_XL = 32
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def DEx_Icon(
|
|
14
|
+
name: str,
|
|
15
|
+
size: int = ICON_MD,
|
|
16
|
+
color: str | None = None,
|
|
17
|
+
tooltip: str = "",
|
|
18
|
+
) -> ft.Icon:
|
|
19
|
+
"""Design-token icon with default color from DEx palette."""
|
|
20
|
+
return ft.Icon(
|
|
21
|
+
name,
|
|
22
|
+
size=size,
|
|
23
|
+
color=color or Colors.TEXT_SECONDARY,
|
|
24
|
+
tooltip=tooltip if tooltip else None,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def DEx_IconBadge(
|
|
29
|
+
name: str,
|
|
30
|
+
badge_text: str,
|
|
31
|
+
size: int = ICON_MD,
|
|
32
|
+
color: str | None = None,
|
|
33
|
+
badge_color: str | None = None,
|
|
34
|
+
) -> ft.Stack:
|
|
35
|
+
"""Icon with a small badge overlay in the top-right corner."""
|
|
36
|
+
return ft.Stack([
|
|
37
|
+
ft.Icon(name, size=size, color=color or Colors.TEXT_SECONDARY),
|
|
38
|
+
ft.Container(
|
|
39
|
+
content=ft.Text(badge_text, size=8, weight=ft.FontWeight.W_700, color=Colors.BG_DEEP),
|
|
40
|
+
bgcolor=badge_color or Colors.DANGER,
|
|
41
|
+
border_radius=6,
|
|
42
|
+
padding=ft.Padding.symmetric(horizontal=3, vertical=1),
|
|
43
|
+
right=0,
|
|
44
|
+
top=0,
|
|
45
|
+
),
|
|
46
|
+
], width=size + 8, height=size + 6)
|
|
@@ -0,0 +1,55 @@
|
|
|
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_Image(
|
|
8
|
+
src: str,
|
|
9
|
+
width: int | None = None,
|
|
10
|
+
height: int | None = None,
|
|
11
|
+
fit=None,
|
|
12
|
+
border_radius: int = 0,
|
|
13
|
+
alt: str = "",
|
|
14
|
+
expand: bool = False,
|
|
15
|
+
) -> ft.Container:
|
|
16
|
+
"""Styled image with optional rounded corners and cover/contain fit."""
|
|
17
|
+
img = ft.Image(
|
|
18
|
+
src=src,
|
|
19
|
+
width=width,
|
|
20
|
+
height=height,
|
|
21
|
+
fit=fit,
|
|
22
|
+
expand=expand,
|
|
23
|
+
)
|
|
24
|
+
if border_radius:
|
|
25
|
+
return ft.Container(
|
|
26
|
+
content=img,
|
|
27
|
+
width=width,
|
|
28
|
+
height=height,
|
|
29
|
+
border_radius=border_radius,
|
|
30
|
+
clip_behavior=ft.ClipBehavior.HARD_EDGE,
|
|
31
|
+
expand=expand,
|
|
32
|
+
)
|
|
33
|
+
return ft.Container(content=img, width=width, height=height, expand=expand)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def DEx_ImagePlaceholder(
|
|
37
|
+
width: int = 200,
|
|
38
|
+
height: int = 120,
|
|
39
|
+
icon: str = ft.Icons.IMAGE_ROUNDED,
|
|
40
|
+
label: str = "Sem imagem",
|
|
41
|
+
border_radius: int = 0,
|
|
42
|
+
) -> ft.Container:
|
|
43
|
+
"""Placeholder shown when no image is available."""
|
|
44
|
+
return ft.Container(
|
|
45
|
+
content=ft.Column([
|
|
46
|
+
ft.Icon(icon, size=28, color=Colors.TEXT_MUTED),
|
|
47
|
+
ft.Text(label, size=11, color=Colors.TEXT_MUTED),
|
|
48
|
+
], horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
49
|
+
alignment=ft.MainAxisAlignment.CENTER, spacing=4),
|
|
50
|
+
width=width,
|
|
51
|
+
height=height,
|
|
52
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
53
|
+
border=ft.Border.all(1, Colors.BORDER),
|
|
54
|
+
border_radius=border_radius,
|
|
55
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
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_Markdown(
|
|
8
|
+
content: str,
|
|
9
|
+
selectable: bool = False,
|
|
10
|
+
expand: bool = False,
|
|
11
|
+
width: int | None = None,
|
|
12
|
+
) -> ft.Markdown:
|
|
13
|
+
"""Styled Markdown renderer using DEx color tokens."""
|
|
14
|
+
return ft.Markdown(
|
|
15
|
+
value=content,
|
|
16
|
+
selectable=selectable,
|
|
17
|
+
expand=expand,
|
|
18
|
+
width=width,
|
|
19
|
+
code_theme="atom-one-dark",
|
|
20
|
+
extension_set=ft.MarkdownExtensionSet.GITHUB_WEB,
|
|
21
|
+
)
|