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
components/__init__.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""FrameWork — public API root."""
|
|
2
|
+
|
|
3
|
+
from components.theme.DEx_Theme import Colors, Spacing, build_theme, build_theme_from_color_theme
|
|
4
|
+
from components.theme.DEx_ColorThemes import COLOR_THEMES, DEx_ColorTheme
|
|
5
|
+
|
|
6
|
+
from components.primitives.DEx_Badge import DEx_Badge
|
|
7
|
+
from components.primitives.DEx_Divider import DEx_Divider
|
|
8
|
+
from components.primitives.DEx_SectionEyebrow import DEx_SectionEyebrow
|
|
9
|
+
from components.primitives.DEx_StatusDot import DEx_StatusDot
|
|
10
|
+
from components.primitives.DEx_Avatar import DEx_Avatar, DEx_AvatarGroup
|
|
11
|
+
from components.primitives.DEx_Image import DEx_Image, DEx_ImagePlaceholder
|
|
12
|
+
from components.primitives.DEx_Icon import DEx_Icon, DEx_IconBadge
|
|
13
|
+
from components.primitives.DEx_Markdown import DEx_Markdown
|
|
14
|
+
|
|
15
|
+
from components.cards.DEx_Card import DEx_Card
|
|
16
|
+
from components.cards.DEx_MetricCard import DEx_MetricCard
|
|
17
|
+
from components.cards.DEx_ContactCard import DEx_ContactCard
|
|
18
|
+
|
|
19
|
+
from components.buttons.DEx_BtnPrimary import DEx_BtnPrimary
|
|
20
|
+
from components.buttons.DEx_BtnGhost import DEx_BtnGhost
|
|
21
|
+
from components.buttons.DEx_BtnIcon import DEx_BtnIcon
|
|
22
|
+
from components.buttons.DEx_BtnFilled import DEx_BtnFilled
|
|
23
|
+
from components.buttons.DEx_BtnTonal import DEx_BtnTonal
|
|
24
|
+
from components.buttons.DEx_BtnText import DEx_BtnText
|
|
25
|
+
from components.buttons.DEx_BtnFAB import DEx_BtnFAB
|
|
26
|
+
from components.buttons.DEx_BtnSegmented import DEx_BtnSegmented
|
|
27
|
+
from components.buttons.DEx_MenuBar import DEx_MenuBar
|
|
28
|
+
from components.buttons.DEx_PopupMenu import DEx_PopupMenu
|
|
29
|
+
|
|
30
|
+
from components.inputs.DEx_TextField import DEx_TextField
|
|
31
|
+
from components.inputs.DEx_SearchField import DEx_SearchField
|
|
32
|
+
from components.inputs.DEx_Dropdown import DEx_Dropdown
|
|
33
|
+
from components.inputs.DEx_Checkbox import DEx_Checkbox
|
|
34
|
+
from components.inputs.DEx_Switch import DEx_Switch
|
|
35
|
+
from components.inputs.DEx_Radio import DEx_Radio
|
|
36
|
+
from components.inputs.DEx_Slider import DEx_Slider
|
|
37
|
+
from components.inputs.DEx_Chip import DEx_Chip
|
|
38
|
+
from components.inputs.DEx_AutoComplete import DEx_AutoComplete
|
|
39
|
+
from components.inputs.DEx_SearchBar import DEx_SearchBar
|
|
40
|
+
from components.inputs.DEx_DatePicker import DEx_DateField, DEx_DateRangeField
|
|
41
|
+
from components.inputs.DEx_TimePicker import DEx_TimeField, DEx_TimeRangeField
|
|
42
|
+
from components.inputs.DEx_FilePicker import DEx_FilePicker, DEx_FilePickerButton
|
|
43
|
+
|
|
44
|
+
from components.data.DEx_DataTable import DEx_DataTable
|
|
45
|
+
from components.data.DEx_PipelineColumn import DEx_PipelineColumn
|
|
46
|
+
from components.data.DEx_TreeView import DEx_TreeView
|
|
47
|
+
from components.data.DEx_ListTile import DEx_ListTile
|
|
48
|
+
from components.data.DEx_ExpansionTile import DEx_ExpansionTile
|
|
49
|
+
from components.data.DEx_ExpansionPanel import DEx_ExpansionPanel
|
|
50
|
+
from components.data.DEx_ListView import DEx_ListView
|
|
51
|
+
from components.data.DEx_GridView import DEx_GridView
|
|
52
|
+
|
|
53
|
+
from components.layout.DEx_AppShell import DEx_AppShell
|
|
54
|
+
from components.layout.DEx_Sidebar import DEx_Sidebar
|
|
55
|
+
from components.layout.DEx_Header import DEx_Header
|
|
56
|
+
from components.layout.DEx_NavItem import DEx_NavItem
|
|
57
|
+
from components.layout.DEx_Tabs import DEx_Tabs, DEx_Tab
|
|
58
|
+
from components.layout.DEx_NavigationBar import DEx_NavigationBar
|
|
59
|
+
from components.layout.DEx_NavigationRail import DEx_NavigationRail
|
|
60
|
+
from components.layout.DEx_NavigationDrawer import DEx_NavigationDrawer
|
|
61
|
+
from components.layout.DEx_AppBar import DEx_AppBar, DEx_BottomAppBar
|
|
62
|
+
from components.layout.DEx_Stack import DEx_Stack, DEx_StackOverlay, DEx_StackBadge
|
|
63
|
+
from components.layout.DEx_ResponsiveRow import DEx_ResponsiveRow, DEx_Col
|
|
64
|
+
from components.layout.DEx_GestureDetector import DEx_GestureDetector, DEx_Hoverable
|
|
65
|
+
from components.layout.DEx_Draggable import DEx_Draggable, DEx_DragTarget
|
|
66
|
+
from components.layout.DEx_AnimatedSwitcher import DEx_AnimatedSwitcher, DEx_AnimatedContainer
|
|
67
|
+
|
|
68
|
+
from components.feedback.DEx_Notify import DEx_Notify
|
|
69
|
+
from components.feedback.DEx_AlertDialog import DEx_AlertDialog
|
|
70
|
+
from components.feedback.DEx_BottomSheet import DEx_BottomSheet
|
|
71
|
+
from components.feedback.DEx_Banner import DEx_Banner
|
|
72
|
+
from components.feedback.DEx_Tooltip import DEx_Tooltip
|
|
73
|
+
from components.feedback.DEx_ProgressBar import DEx_ProgressBar
|
|
74
|
+
from components.feedback.DEx_ProgressRing import DEx_ProgressRing
|
|
75
|
+
from components.feedback.DEx_Shimmer import DEx_ShimmerCard, DEx_ShimmerListItem, DEx_ShimmerList
|
|
76
|
+
|
|
77
|
+
__version__ = "0.1.1"
|
|
78
|
+
|
|
79
|
+
__all__ = [
|
|
80
|
+
# theme
|
|
81
|
+
"Colors", "Spacing", "build_theme", "build_theme_from_color_theme", "COLOR_THEMES", "DEx_ColorTheme",
|
|
82
|
+
# primitives
|
|
83
|
+
"DEx_Badge", "DEx_Divider", "DEx_SectionEyebrow", "DEx_StatusDot",
|
|
84
|
+
"DEx_Avatar", "DEx_AvatarGroup", "DEx_Image", "DEx_ImagePlaceholder",
|
|
85
|
+
"DEx_Icon", "DEx_IconBadge", "DEx_Markdown",
|
|
86
|
+
# cards
|
|
87
|
+
"DEx_Card", "DEx_MetricCard", "DEx_ContactCard",
|
|
88
|
+
# buttons
|
|
89
|
+
"DEx_BtnPrimary", "DEx_BtnGhost", "DEx_BtnIcon", "DEx_BtnFilled",
|
|
90
|
+
"DEx_BtnTonal", "DEx_BtnText", "DEx_BtnFAB", "DEx_BtnSegmented",
|
|
91
|
+
"DEx_MenuBar", "DEx_PopupMenu",
|
|
92
|
+
# inputs
|
|
93
|
+
"DEx_TextField", "DEx_SearchField", "DEx_Dropdown", "DEx_Checkbox",
|
|
94
|
+
"DEx_Switch", "DEx_Radio", "DEx_Slider", "DEx_Chip",
|
|
95
|
+
"DEx_AutoComplete", "DEx_SearchBar",
|
|
96
|
+
"DEx_DateField", "DEx_DateRangeField",
|
|
97
|
+
"DEx_TimeField", "DEx_TimeRangeField",
|
|
98
|
+
"DEx_FilePicker", "DEx_FilePickerButton",
|
|
99
|
+
# data
|
|
100
|
+
"DEx_DataTable", "DEx_PipelineColumn", "DEx_TreeView",
|
|
101
|
+
"DEx_ListTile", "DEx_ExpansionTile", "DEx_ExpansionPanel",
|
|
102
|
+
"DEx_ListView", "DEx_GridView",
|
|
103
|
+
# layout
|
|
104
|
+
"DEx_AppShell", "DEx_Sidebar", "DEx_Header", "DEx_NavItem",
|
|
105
|
+
"DEx_Tabs", "DEx_Tab",
|
|
106
|
+
"DEx_NavigationBar", "DEx_NavigationRail", "DEx_NavigationDrawer", "DEx_AppBar",
|
|
107
|
+
"DEx_BottomAppBar",
|
|
108
|
+
"DEx_Stack", "DEx_StackOverlay", "DEx_StackBadge",
|
|
109
|
+
"DEx_ResponsiveRow", "DEx_Col",
|
|
110
|
+
"DEx_GestureDetector", "DEx_Hoverable",
|
|
111
|
+
"DEx_Draggable", "DEx_DragTarget",
|
|
112
|
+
"DEx_AnimatedSwitcher", "DEx_AnimatedContainer",
|
|
113
|
+
# feedback
|
|
114
|
+
"DEx_Notify", "DEx_AlertDialog", "DEx_BottomSheet", "DEx_Banner",
|
|
115
|
+
"DEx_Tooltip", "DEx_ProgressBar", "DEx_ProgressRing",
|
|
116
|
+
"DEx_ShimmerCard", "DEx_ShimmerListItem", "DEx_ShimmerList",
|
|
117
|
+
# meta
|
|
118
|
+
"__version__",
|
|
119
|
+
]
|
|
@@ -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_BtnFAB(
|
|
8
|
+
icon: str,
|
|
9
|
+
label: str = "",
|
|
10
|
+
on_click=None,
|
|
11
|
+
mini: bool = False,
|
|
12
|
+
) -> ft.FloatingActionButton:
|
|
13
|
+
"""Floating action button for the primary creation or positive action on a screen.
|
|
14
|
+
|
|
15
|
+
Pass label for extended FAB; omit for compact circular form.
|
|
16
|
+
"""
|
|
17
|
+
if label:
|
|
18
|
+
return ft.FloatingActionButton(
|
|
19
|
+
content=ft.Row(
|
|
20
|
+
[ft.Icon(icon, size=18, color=Colors.TEXT_PRIMARY), ft.Text(label, size=14, color=Colors.TEXT_PRIMARY)],
|
|
21
|
+
spacing=8,
|
|
22
|
+
tight=True,
|
|
23
|
+
),
|
|
24
|
+
on_click=on_click,
|
|
25
|
+
bgcolor=Colors.ACCENT,
|
|
26
|
+
hover_color=Colors.ACCENT_DARK,
|
|
27
|
+
shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
28
|
+
)
|
|
29
|
+
return ft.FloatingActionButton(
|
|
30
|
+
icon=icon,
|
|
31
|
+
on_click=on_click,
|
|
32
|
+
mini=mini,
|
|
33
|
+
bgcolor=Colors.ACCENT,
|
|
34
|
+
foreground_color=Colors.TEXT_PRIMARY,
|
|
35
|
+
hover_color=Colors.ACCENT_DARK,
|
|
36
|
+
shape=ft.RoundedRectangleBorder(radius=28),
|
|
37
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
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_BtnFilled(label: str, on_click=None, icon=None) -> ft.FilledButton:
|
|
8
|
+
"""Primary filled button — highest emphasis action in a view."""
|
|
9
|
+
return ft.FilledButton(
|
|
10
|
+
content=label,
|
|
11
|
+
icon=icon,
|
|
12
|
+
on_click=on_click,
|
|
13
|
+
style=ft.ButtonStyle(
|
|
14
|
+
bgcolor=Colors.ACCENT,
|
|
15
|
+
color=Colors.TEXT_PRIMARY,
|
|
16
|
+
overlay_color=Colors.ACCENT_DARK,
|
|
17
|
+
shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
18
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.SM + 2),
|
|
19
|
+
),
|
|
20
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
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_BtnGhost(label: str, on_click=None, icon=None) -> ft.OutlinedButton:
|
|
8
|
+
"""Create the versioned secondary outlined button for lower-emphasis actions."""
|
|
9
|
+
return ft.OutlinedButton(
|
|
10
|
+
content=label,
|
|
11
|
+
icon=icon,
|
|
12
|
+
on_click=on_click,
|
|
13
|
+
style=ft.ButtonStyle(
|
|
14
|
+
color=Colors.TEXT_PRIMARY,
|
|
15
|
+
side=ft.BorderSide(1, Colors.BORDER),
|
|
16
|
+
overlay_color=Colors.BG_ELEVATED,
|
|
17
|
+
shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
18
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.SM + 2),
|
|
19
|
+
),
|
|
20
|
+
)
|
|
@@ -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_BtnIcon(icon, on_click=None, tooltip: str = "") -> ft.IconButton:
|
|
8
|
+
"""Create the versioned icon-only action button with optional tooltip."""
|
|
9
|
+
return ft.IconButton(
|
|
10
|
+
icon=icon,
|
|
11
|
+
icon_color=Colors.TEXT_SECONDARY,
|
|
12
|
+
tooltip=tooltip,
|
|
13
|
+
on_click=on_click,
|
|
14
|
+
style=ft.ButtonStyle(
|
|
15
|
+
overlay_color=Colors.BG_ELEVATED,
|
|
16
|
+
shape=ft.RoundedRectangleBorder(radius=6),
|
|
17
|
+
),
|
|
18
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
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_BtnPrimary(label: str, on_click=None, icon=None) -> ft.Button:
|
|
8
|
+
"""Create the versioned primary action button for main user commands."""
|
|
9
|
+
return ft.Button(
|
|
10
|
+
content=label,
|
|
11
|
+
icon=icon,
|
|
12
|
+
on_click=on_click,
|
|
13
|
+
bgcolor=Colors.ACCENT,
|
|
14
|
+
color=Colors.TEXT_PRIMARY,
|
|
15
|
+
elevation=1,
|
|
16
|
+
style=ft.ButtonStyle(
|
|
17
|
+
overlay_color=Colors.ACCENT_DARK,
|
|
18
|
+
shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
19
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.SM + 2),
|
|
20
|
+
),
|
|
21
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
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_BtnSegmented(
|
|
8
|
+
segments: list[dict],
|
|
9
|
+
selected: list[str] | set[str] | None = None,
|
|
10
|
+
on_change=None,
|
|
11
|
+
allow_multiple: bool = False,
|
|
12
|
+
allow_empty: bool = False,
|
|
13
|
+
) -> ft.SegmentedButton:
|
|
14
|
+
"""Multi-option toggle control for mutually exclusive or multi-select choices.
|
|
15
|
+
|
|
16
|
+
Each entry in segments: {"value": str, "label": str, "icon": str | None}
|
|
17
|
+
"""
|
|
18
|
+
_selected = list(selected) if selected else [segments[0]["value"]]
|
|
19
|
+
return ft.SegmentedButton(
|
|
20
|
+
segments=[
|
|
21
|
+
ft.Segment(
|
|
22
|
+
value=s["value"],
|
|
23
|
+
label=ft.Text(s["label"], size=13),
|
|
24
|
+
icon=ft.Icon(s["icon"]) if s.get("icon") else None,
|
|
25
|
+
)
|
|
26
|
+
for s in segments
|
|
27
|
+
],
|
|
28
|
+
selected=_selected,
|
|
29
|
+
on_change=on_change,
|
|
30
|
+
allow_multiple_selection=allow_multiple,
|
|
31
|
+
allow_empty_selection=allow_empty,
|
|
32
|
+
style=ft.ButtonStyle(
|
|
33
|
+
bgcolor={
|
|
34
|
+
ft.ControlState.SELECTED: Colors.ACCENT,
|
|
35
|
+
ft.ControlState.DEFAULT: Colors.BG_ELEVATED,
|
|
36
|
+
},
|
|
37
|
+
color={
|
|
38
|
+
ft.ControlState.SELECTED: Colors.TEXT_PRIMARY,
|
|
39
|
+
ft.ControlState.DEFAULT: Colors.TEXT_SECONDARY,
|
|
40
|
+
},
|
|
41
|
+
side=ft.BorderSide(1, Colors.BORDER),
|
|
42
|
+
shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
43
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.SM),
|
|
44
|
+
),
|
|
45
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
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_BtnText(label: str, on_click=None, icon=None) -> ft.TextButton:
|
|
8
|
+
"""Lowest-emphasis action button — no border, no background fill."""
|
|
9
|
+
return ft.TextButton(
|
|
10
|
+
content=label,
|
|
11
|
+
icon=icon,
|
|
12
|
+
on_click=on_click,
|
|
13
|
+
style=ft.ButtonStyle(
|
|
14
|
+
color=Colors.ACCENT,
|
|
15
|
+
overlay_color=Colors.BG_ELEVATED,
|
|
16
|
+
shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
17
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.SM, vertical=Spacing.SM + 2),
|
|
18
|
+
),
|
|
19
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
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_BtnTonal(label: str, on_click=None, icon=None) -> ft.FilledTonalButton:
|
|
8
|
+
"""Secondary filled button with muted emphasis — between outlined and filled."""
|
|
9
|
+
return ft.FilledTonalButton(
|
|
10
|
+
content=label,
|
|
11
|
+
icon=icon,
|
|
12
|
+
on_click=on_click,
|
|
13
|
+
style=ft.ButtonStyle(
|
|
14
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
15
|
+
color=Colors.ACCENT,
|
|
16
|
+
overlay_color=Colors.ACCENT_DARK,
|
|
17
|
+
shape=ft.RoundedRectangleBorder(radius=Spacing.BUTTON_BORDER_RADIUS),
|
|
18
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.SM + 2),
|
|
19
|
+
),
|
|
20
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
|
|
4
|
+
COMPONENT_VERSION = "0.0.1"
|
|
5
|
+
|
|
6
|
+
_ITEM_STYLE = ft.ButtonStyle(
|
|
7
|
+
bgcolor={
|
|
8
|
+
ft.ControlState.HOVERED: Colors.BG_ELEVATED,
|
|
9
|
+
ft.ControlState.DEFAULT: Colors.BG_SURFACE,
|
|
10
|
+
},
|
|
11
|
+
color=Colors.TEXT_PRIMARY,
|
|
12
|
+
overlay_color=Colors.BG_ELEVATED,
|
|
13
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.SM),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def DEx_MenuBarItem(
|
|
18
|
+
label: str,
|
|
19
|
+
children: list[ft.MenuItemButton | ft.SubmenuButton],
|
|
20
|
+
) -> ft.SubmenuButton:
|
|
21
|
+
"""Top-level menu entry inside a DEx_MenuBar."""
|
|
22
|
+
return ft.SubmenuButton(
|
|
23
|
+
content=ft.Text(label, size=13, color=Colors.TEXT_PRIMARY),
|
|
24
|
+
controls=children,
|
|
25
|
+
style=_ITEM_STYLE,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def DEx_MenuItem(
|
|
30
|
+
label: str,
|
|
31
|
+
icon: str | None = None,
|
|
32
|
+
on_click=None,
|
|
33
|
+
shortcut: str = "",
|
|
34
|
+
) -> ft.MenuItemButton:
|
|
35
|
+
"""Single clickable item inside a submenu."""
|
|
36
|
+
return ft.MenuItemButton(
|
|
37
|
+
content=ft.Row(
|
|
38
|
+
[
|
|
39
|
+
ft.Text(label, size=13, color=Colors.TEXT_PRIMARY, expand=True),
|
|
40
|
+
ft.Text(shortcut, size=11, color=Colors.TEXT_MUTED) if shortcut else ft.Container(),
|
|
41
|
+
],
|
|
42
|
+
spacing=Spacing.MD,
|
|
43
|
+
),
|
|
44
|
+
leading=ft.Icon(icon, size=16, color=Colors.TEXT_SECONDARY) if icon else None,
|
|
45
|
+
on_click=on_click,
|
|
46
|
+
style=_ITEM_STYLE,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def DEx_MenuBar(items: list[ft.SubmenuButton]) -> ft.MenuBar:
|
|
51
|
+
"""Horizontal application menu bar styled with DEx tokens."""
|
|
52
|
+
return ft.MenuBar(
|
|
53
|
+
controls=items,
|
|
54
|
+
style=ft.MenuStyle(
|
|
55
|
+
bgcolor=Colors.BG_SURFACE,
|
|
56
|
+
shadow_color=ft.Colors.TRANSPARENT,
|
|
57
|
+
side=ft.BorderSide(0, ft.Colors.TRANSPARENT),
|
|
58
|
+
shape=ft.RoundedRectangleBorder(radius=0),
|
|
59
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.SM, vertical=0),
|
|
60
|
+
),
|
|
61
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
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_PopupMenuItem(
|
|
8
|
+
label: str,
|
|
9
|
+
icon: str | None = None,
|
|
10
|
+
on_click=None,
|
|
11
|
+
checked: bool | None = None,
|
|
12
|
+
) -> ft.PopupMenuItem:
|
|
13
|
+
"""Single item inside a DEx_PopupMenu."""
|
|
14
|
+
row_items: list[ft.Control] = []
|
|
15
|
+
if icon:
|
|
16
|
+
row_items.append(ft.Icon(icon, size=16, color=Colors.TEXT_SECONDARY))
|
|
17
|
+
row_items.append(ft.Text(label, size=13, color=Colors.TEXT_PRIMARY))
|
|
18
|
+
return ft.PopupMenuItem(
|
|
19
|
+
content=ft.Row(row_items, spacing=Spacing.SM, tight=True),
|
|
20
|
+
on_click=on_click,
|
|
21
|
+
checked=checked,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def DEx_PopupMenuDivider() -> ft.PopupMenuItem:
|
|
26
|
+
"""Visual separator between groups of popup menu items."""
|
|
27
|
+
return ft.PopupMenuItem()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def DEx_PopupMenu(
|
|
31
|
+
items: list[ft.PopupMenuItem],
|
|
32
|
+
icon: str = ft.Icons.MORE_VERT,
|
|
33
|
+
tooltip: str = "Mais opções",
|
|
34
|
+
icon_color: str | None = None,
|
|
35
|
+
) -> ft.PopupMenuButton:
|
|
36
|
+
"""Context/overflow menu triggered by an icon button."""
|
|
37
|
+
return ft.PopupMenuButton(
|
|
38
|
+
icon=icon,
|
|
39
|
+
icon_color=icon_color or Colors.TEXT_SECONDARY,
|
|
40
|
+
tooltip=tooltip,
|
|
41
|
+
items=items,
|
|
42
|
+
menu_position=ft.PopupMenuPosition.UNDER,
|
|
43
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
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_Card(
|
|
8
|
+
content: ft.Control,
|
|
9
|
+
padding: int = Spacing.MD,
|
|
10
|
+
elevated: bool = False,
|
|
11
|
+
width: int = 300,
|
|
12
|
+
expand: bool = False,
|
|
13
|
+
) -> ft.Container:
|
|
14
|
+
"""Create the versioned base card surface used by higher-level components."""
|
|
15
|
+
return ft.Container(
|
|
16
|
+
content=content,
|
|
17
|
+
bgcolor=Colors.BG_ELEVATED if elevated else Colors.BG_SURFACE,
|
|
18
|
+
border=ft.Border.all(1, Colors.BORDER),
|
|
19
|
+
border_radius=Spacing.CARD_BORDER_RADIUS,
|
|
20
|
+
padding=padding,
|
|
21
|
+
width=None if expand else width,
|
|
22
|
+
expand=expand,
|
|
23
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
from components.cards.DEx_Card import DEx_Card
|
|
4
|
+
from components.primitives.DEx_Badge import DEx_Badge
|
|
5
|
+
from components.primitives.DEx_Divider import DEx_Divider
|
|
6
|
+
|
|
7
|
+
COMPONENT_VERSION = "0.0.1"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def DEx_ContactCard(
|
|
11
|
+
name: str,
|
|
12
|
+
role: str,
|
|
13
|
+
company: str,
|
|
14
|
+
email: str = None,
|
|
15
|
+
tag: str = None,
|
|
16
|
+
value: str = None,
|
|
17
|
+
) -> ft.Container:
|
|
18
|
+
"""Create the versioned contact/entity card with avatar and optional footer."""
|
|
19
|
+
initials = "".join(p[0].upper() for p in name.split()[:2])
|
|
20
|
+
|
|
21
|
+
avatar = ft.Container(
|
|
22
|
+
content=ft.Text(initials, size=14, weight=ft.FontWeight.W_700, color=Colors.ACCENT),
|
|
23
|
+
width=42, height=42,
|
|
24
|
+
bgcolor=f"{Colors.ACCENT}22",
|
|
25
|
+
border_radius=21,
|
|
26
|
+
alignment=ft.Alignment(0, 0),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
info = ft.Column([
|
|
30
|
+
ft.Text(name, size=14, weight=ft.FontWeight.W_600, color=Colors.TEXT_PRIMARY),
|
|
31
|
+
ft.Text(f"{role} · {company}", size=12, color=Colors.TEXT_SECONDARY),
|
|
32
|
+
ft.Text(email, size=12, color=Colors.DATA) if email else ft.Container(),
|
|
33
|
+
], spacing=2, tight=True)
|
|
34
|
+
|
|
35
|
+
footer_controls = []
|
|
36
|
+
if tag:
|
|
37
|
+
footer_controls.append(
|
|
38
|
+
DEx_Badge(tag, color=f"{Colors.ACCENT}33", text_color=Colors.ACCENT)
|
|
39
|
+
)
|
|
40
|
+
if value:
|
|
41
|
+
footer_controls.append(
|
|
42
|
+
ft.Text(value, size=13, weight=ft.FontWeight.W_700, color=Colors.TEXT_PRIMARY)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return DEx_Card(ft.Column([
|
|
46
|
+
ft.Row([avatar, info], spacing=Spacing.MD),
|
|
47
|
+
DEx_Divider() if footer_controls else ft.Container(),
|
|
48
|
+
ft.Row(
|
|
49
|
+
footer_controls,
|
|
50
|
+
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
|
51
|
+
) if footer_controls else ft.Container(),
|
|
52
|
+
], spacing=Spacing.SM, tight=True))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import flet as ft
|
|
2
|
+
from components.theme.DEx_Theme import Colors, Spacing
|
|
3
|
+
from components.cards.DEx_Card import DEx_Card
|
|
4
|
+
from components.primitives.DEx_StatusDot import DEx_StatusDot
|
|
5
|
+
|
|
6
|
+
COMPONENT_VERSION = "0.0.1"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def DEx_MetricCard(
|
|
10
|
+
label: str,
|
|
11
|
+
value: str,
|
|
12
|
+
delta: str = None,
|
|
13
|
+
delta_positive: bool = True,
|
|
14
|
+
icon=None,
|
|
15
|
+
) -> ft.Container:
|
|
16
|
+
"""Create the versioned metric card for KPI values and period deltas."""
|
|
17
|
+
delta_color = Colors.SUCCESS if delta_positive else Colors.DANGER
|
|
18
|
+
|
|
19
|
+
header_row = [ft.Text(label, size=12, color=Colors.TEXT_SECONDARY, weight=ft.FontWeight.W_500)]
|
|
20
|
+
if icon:
|
|
21
|
+
header_row.insert(0, ft.Icon(icon, size=14, color=Colors.TEXT_MUTED))
|
|
22
|
+
|
|
23
|
+
controls = [
|
|
24
|
+
ft.Row(header_row, spacing=Spacing.XS),
|
|
25
|
+
ft.Text(value, size=26, weight=ft.FontWeight.W_700, color=Colors.TEXT_PRIMARY),
|
|
26
|
+
]
|
|
27
|
+
if delta:
|
|
28
|
+
controls.append(
|
|
29
|
+
ft.Row([
|
|
30
|
+
DEx_StatusDot(delta_color),
|
|
31
|
+
ft.Text(delta, size=12, color=delta_color, weight=ft.FontWeight.W_600),
|
|
32
|
+
ft.Text("vs período anterior", size=12, color=Colors.TEXT_MUTED),
|
|
33
|
+
], spacing=Spacing.XS)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return DEx_Card(ft.Column(controls, spacing=Spacing.SM, tight=True))
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
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 _state_placeholder(state: str, empty_message: str, error_message: str) -> ft.Control:
|
|
8
|
+
if state == "loading":
|
|
9
|
+
return ft.Container(
|
|
10
|
+
content=ft.Column(
|
|
11
|
+
[ft.ProgressRing(width=32, height=32, stroke_width=3, color=Colors.ACCENT)],
|
|
12
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
13
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
14
|
+
),
|
|
15
|
+
padding=Spacing.XL,
|
|
16
|
+
)
|
|
17
|
+
if state == "empty":
|
|
18
|
+
return ft.Container(
|
|
19
|
+
content=ft.Column(
|
|
20
|
+
[ft.Icon(ft.Icons.INBOX_ROUNDED, size=40, color=Colors.TEXT_MUTED),
|
|
21
|
+
ft.Text(empty_message, size=13, color=Colors.TEXT_MUTED)],
|
|
22
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
23
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
24
|
+
spacing=Spacing.SM,
|
|
25
|
+
),
|
|
26
|
+
padding=Spacing.XL,
|
|
27
|
+
)
|
|
28
|
+
return ft.Container(
|
|
29
|
+
content=ft.Column(
|
|
30
|
+
[ft.Icon(ft.Icons.ERROR_ROUNDED, size=40, color=Colors.DANGER),
|
|
31
|
+
ft.Text(error_message, size=13, color=Colors.DANGER)],
|
|
32
|
+
alignment=ft.MainAxisAlignment.CENTER,
|
|
33
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
34
|
+
spacing=Spacing.SM,
|
|
35
|
+
),
|
|
36
|
+
padding=Spacing.XL,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def DEx_DataTable(
|
|
41
|
+
columns: list[str],
|
|
42
|
+
rows: list[list[str]],
|
|
43
|
+
on_row_click=None,
|
|
44
|
+
state: str | None = None,
|
|
45
|
+
empty_message: str = "Nenhum registro encontrado.",
|
|
46
|
+
error_message: str = "Erro ao carregar os dados.",
|
|
47
|
+
) -> ft.Control:
|
|
48
|
+
"""Create the versioned data table with DEx headers, borders, and rows."""
|
|
49
|
+
if state in ("loading", "empty", "error"):
|
|
50
|
+
return _state_placeholder(state, empty_message, error_message)
|
|
51
|
+
return ft.DataTable(
|
|
52
|
+
columns=[
|
|
53
|
+
ft.DataColumn(
|
|
54
|
+
ft.Text(
|
|
55
|
+
col.upper(),
|
|
56
|
+
size=11,
|
|
57
|
+
weight=ft.FontWeight.W_600,
|
|
58
|
+
color=Colors.TEXT_MUTED,
|
|
59
|
+
style=ft.TextStyle(letter_spacing=1.2),
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
for col in columns
|
|
63
|
+
],
|
|
64
|
+
rows=[
|
|
65
|
+
ft.DataRow(
|
|
66
|
+
cells=[
|
|
67
|
+
ft.DataCell(ft.Text(cell, size=13, color=Colors.TEXT_PRIMARY))
|
|
68
|
+
for cell in row
|
|
69
|
+
],
|
|
70
|
+
on_select_change=on_row_click,
|
|
71
|
+
)
|
|
72
|
+
for row in rows
|
|
73
|
+
],
|
|
74
|
+
border=ft.Border.all(1, Colors.BORDER),
|
|
75
|
+
border_radius=Spacing.CARD_BORDER_RADIUS,
|
|
76
|
+
horizontal_lines=ft.BorderSide(1, Colors.BORDER),
|
|
77
|
+
heading_row_color=Colors.BG_ELEVATED,
|
|
78
|
+
heading_row_height=44,
|
|
79
|
+
data_row_min_height=48,
|
|
80
|
+
data_row_max_height=56,
|
|
81
|
+
column_spacing=Spacing.LG,
|
|
82
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
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_ExpansionPanel(
|
|
8
|
+
title: str,
|
|
9
|
+
content: ft.Control,
|
|
10
|
+
subtitle: str = "",
|
|
11
|
+
expanded: bool = False,
|
|
12
|
+
) -> ft.ExpansionPanel:
|
|
13
|
+
"""Single panel for use inside DEx_ExpansionPanelList."""
|
|
14
|
+
header = ft.ListTile(
|
|
15
|
+
title=ft.Text(title, size=14, color=Colors.TEXT_PRIMARY),
|
|
16
|
+
subtitle=ft.Text(subtitle, size=11, color=Colors.TEXT_SECONDARY) if subtitle else None,
|
|
17
|
+
)
|
|
18
|
+
return ft.ExpansionPanel(
|
|
19
|
+
header=header,
|
|
20
|
+
content=ft.Container(
|
|
21
|
+
content=content,
|
|
22
|
+
padding=ft.Padding.symmetric(horizontal=Spacing.MD, vertical=Spacing.SM),
|
|
23
|
+
),
|
|
24
|
+
expanded=expanded,
|
|
25
|
+
bgcolor=Colors.BG_ELEVATED,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def DEx_ExpansionPanelList(
|
|
30
|
+
panels: list[ft.ExpansionPanel],
|
|
31
|
+
on_change=None,
|
|
32
|
+
) -> ft.ExpansionPanelList:
|
|
33
|
+
"""Accordion-style list of expandable panels."""
|
|
34
|
+
return ft.ExpansionPanelList(
|
|
35
|
+
controls=panels,
|
|
36
|
+
on_change=on_change,
|
|
37
|
+
divider_color=Colors.BORDER,
|
|
38
|
+
elevation=0,
|
|
39
|
+
)
|