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,22 @@
|
|
|
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_SectionEyebrow(number: str, label: str) -> ft.Row:
|
|
8
|
+
"""Create the versioned numbered cyan section eyebrow."""
|
|
9
|
+
return ft.Row(
|
|
10
|
+
controls=[
|
|
11
|
+
ft.Text(number, size=11, weight=ft.FontWeight.W_700, color=Colors.DATA),
|
|
12
|
+
ft.Container(width=1, height=12, bgcolor=Colors.BORDER),
|
|
13
|
+
ft.Text(
|
|
14
|
+
label.upper(),
|
|
15
|
+
size=11,
|
|
16
|
+
weight=ft.FontWeight.W_600,
|
|
17
|
+
color=Colors.TEXT_MUTED,
|
|
18
|
+
style=ft.TextStyle(letter_spacing=1.5),
|
|
19
|
+
),
|
|
20
|
+
],
|
|
21
|
+
spacing=Spacing.SM,
|
|
22
|
+
)
|
|
File without changes
|
components/project.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Project metadata helpers loaded from pyproject.toml."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import tomllib
|
|
7
|
+
from components.versioning import validate_version
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
11
|
+
PYPROJECT_PATH = PROJECT_ROOT / "pyproject.toml"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_project_version() -> str:
|
|
15
|
+
"""Return the project version declared in pyproject.toml."""
|
|
16
|
+
with PYPROJECT_PATH.open("rb") as file:
|
|
17
|
+
data = tomllib.load(file)
|
|
18
|
+
return validate_version(data["project"]["version"])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_project_version_label() -> str:
|
|
22
|
+
"""Return the display-ready project version label."""
|
|
23
|
+
return f"v{get_project_version()}"
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""Accessible 60-30-10 color themes for DEx applications."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
import flet as ft
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
COMPONENT_VERSION = "0.0.3"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _blend(base: str, overlay: str, amount: float) -> str:
|
|
14
|
+
"""Mix two hex colors by amount (0.0 = base, 1.0 = overlay)."""
|
|
15
|
+
try:
|
|
16
|
+
b = base.strip().lstrip("#")[:6]
|
|
17
|
+
o = overlay.strip().lstrip("#")[:6]
|
|
18
|
+
if len(b) != 6 or len(o) != 6:
|
|
19
|
+
return base
|
|
20
|
+
br, bg_, bb = int(b[0:2], 16), int(b[2:4], 16), int(b[4:6], 16)
|
|
21
|
+
or_, og, ob = int(o[0:2], 16), int(o[2:4], 16), int(o[4:6], 16)
|
|
22
|
+
r = max(0, min(255, round(br + (or_ - br) * amount)))
|
|
23
|
+
g = max(0, min(255, round(bg_ + (og - bg_) * amount)))
|
|
24
|
+
b_ = max(0, min(255, round(bb + (ob - bb) * amount)))
|
|
25
|
+
return f"#{r:02X}{g:02X}{b_:02X}"
|
|
26
|
+
except (ValueError, IndexError):
|
|
27
|
+
return base
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
|
|
31
|
+
value = hex_color.strip().lstrip("#")[:6]
|
|
32
|
+
if len(value) != 6:
|
|
33
|
+
raise ValueError("Invalid hex color")
|
|
34
|
+
return (int(value[0:2], 16), int(value[2:4], 16), int(value[4:6], 16))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _relative_luminance(hex_color: str) -> float:
|
|
38
|
+
r, g, b = _hex_to_rgb(hex_color)
|
|
39
|
+
channels = []
|
|
40
|
+
for value in (r, g, b):
|
|
41
|
+
c = value / 255.0
|
|
42
|
+
channels.append(c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4)
|
|
43
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _pick_on_color(hex_color: str) -> str:
|
|
47
|
+
return "#FFFFFF" if _relative_luminance(hex_color) < 0.45 else "#111111"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _secondary_text(background: str) -> str:
|
|
51
|
+
return "#E2E8F0" if _relative_luminance(background) < 0.35 else "#475569"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _muted_text(background: str) -> str:
|
|
55
|
+
return "#CBD5E1" if _relative_luminance(background) < 0.35 else "#64748B"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _disabled_text(background: str) -> str:
|
|
59
|
+
return "#64748B" if _relative_luminance(background) < 0.35 else "#94A3B8"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class DEx_ColorTheme:
|
|
64
|
+
"""Versioned color theme using 60-30-10 visual distribution."""
|
|
65
|
+
|
|
66
|
+
key: str
|
|
67
|
+
name: str
|
|
68
|
+
description: str
|
|
69
|
+
background: str
|
|
70
|
+
text: str
|
|
71
|
+
accent: str
|
|
72
|
+
contrast_ratio: float
|
|
73
|
+
accessibility: str
|
|
74
|
+
accent_alt: str | None = None
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def bg_primary(self) -> str:
|
|
78
|
+
"""60% dominant background token."""
|
|
79
|
+
return self.background
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def surface(self) -> str:
|
|
83
|
+
"""Primary surface token used for cards and panels."""
|
|
84
|
+
return _blend(self.background, self.text, 0.05)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def surface_hover(self) -> str:
|
|
88
|
+
"""Hover state surface token."""
|
|
89
|
+
return _blend(self.background, self.text, 0.09)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def surface_selected(self) -> str:
|
|
93
|
+
"""Selected state surface token."""
|
|
94
|
+
return _blend(self.background, self.accent, 0.12)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def surface_elevated(self) -> str:
|
|
98
|
+
"""Raised surface token for menus, cards and overlays."""
|
|
99
|
+
return _blend(self.background, self.text, 0.13)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def surface_pressed(self) -> str:
|
|
103
|
+
"""Pressed state surface token."""
|
|
104
|
+
return _blend(self.background, self.text, 0.18)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def surface_disabled(self) -> str:
|
|
108
|
+
"""Disabled state surface token."""
|
|
109
|
+
return _blend(self.background, self.text, 0.03)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def bg_secondary(self) -> str:
|
|
113
|
+
"""Secondary surface token derived from the dominant color."""
|
|
114
|
+
return self.surface
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def text_primary(self) -> str:
|
|
118
|
+
"""Primary foreground text token."""
|
|
119
|
+
return self.text
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def text_secondary(self) -> str:
|
|
123
|
+
"""Secondary foreground text token."""
|
|
124
|
+
return _secondary_text(self.background)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def text_muted(self) -> str:
|
|
128
|
+
"""Muted foreground text token."""
|
|
129
|
+
return _muted_text(self.background)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def text_disabled(self) -> str:
|
|
133
|
+
"""Disabled text token."""
|
|
134
|
+
return _disabled_text(self.background)
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def border(self) -> str:
|
|
138
|
+
"""Structural border token."""
|
|
139
|
+
return _blend(self.background, self.text, 0.22)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def border_soft(self) -> str:
|
|
143
|
+
"""Subtle border token."""
|
|
144
|
+
return _blend(self.background, self.text, 0.12)
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def focus(self) -> str:
|
|
148
|
+
"""Focus ring token."""
|
|
149
|
+
return self.accent_primary
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def accent_primary(self) -> str:
|
|
153
|
+
"""10% accent token for CTA, badges, links, and critical highlights."""
|
|
154
|
+
return self.accent
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def accent_on(self) -> str:
|
|
158
|
+
"""Foreground color that reads on top of the accent token."""
|
|
159
|
+
return _pick_on_color(self.accent_primary)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def accent_soft(self) -> str:
|
|
163
|
+
"""Soft accent surface token."""
|
|
164
|
+
return _blend(self.background, self.accent_primary, 0.16)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def overlay(self) -> str:
|
|
168
|
+
"""Modal overlay token."""
|
|
169
|
+
return "#00000066" if self.is_dark else "#FFFFFF66"
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def is_dark(self) -> bool:
|
|
173
|
+
"""Return whether the theme should be treated as dark."""
|
|
174
|
+
return _relative_luminance(self.background) < 0.35
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def theme_mode(self) -> ft.ThemeMode:
|
|
178
|
+
"""Return the preferred Flet theme mode for this palette."""
|
|
179
|
+
return ft.ThemeMode.DARK if self.is_dark else ft.ThemeMode.LIGHT
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def shadow(self) -> str:
|
|
183
|
+
"""Shadow token for elevated surfaces."""
|
|
184
|
+
return "#00000040" if self.is_dark else "#00000024"
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def data(self) -> str:
|
|
188
|
+
"""Data/highlight accent token."""
|
|
189
|
+
return self.accent_primary
|
|
190
|
+
|
|
191
|
+
def css_variables(self, prefix: str = "--dex") -> dict[str, str]:
|
|
192
|
+
"""Return CSS-style variables for Tailwind or Styled Components usage."""
|
|
193
|
+
return {
|
|
194
|
+
f"{prefix}-bg-primary": self.bg_primary,
|
|
195
|
+
f"{prefix}-bg-secondary": self.bg_secondary,
|
|
196
|
+
f"{prefix}-surface": self.surface,
|
|
197
|
+
f"{prefix}-surface-hover": self.surface_hover,
|
|
198
|
+
f"{prefix}-surface-selected": self.surface_selected,
|
|
199
|
+
f"{prefix}-surface-elevated": self.surface_elevated,
|
|
200
|
+
f"{prefix}-surface-pressed": self.surface_pressed,
|
|
201
|
+
f"{prefix}-surface-disabled": self.surface_disabled,
|
|
202
|
+
f"{prefix}-text-primary": self.text_primary,
|
|
203
|
+
f"{prefix}-text-secondary": self.text_secondary,
|
|
204
|
+
f"{prefix}-text-muted": self.text_muted,
|
|
205
|
+
f"{prefix}-text-disabled": self.text_disabled,
|
|
206
|
+
f"{prefix}-border": self.border,
|
|
207
|
+
f"{prefix}-border-soft": self.border_soft,
|
|
208
|
+
f"{prefix}-focus": self.focus,
|
|
209
|
+
f"{prefix}-accent": self.accent_primary,
|
|
210
|
+
f"{prefix}-accent-alt": self.accent_alt or self.accent_primary,
|
|
211
|
+
f"{prefix}-accent-soft": self.accent_soft,
|
|
212
|
+
f"{prefix}-accent-on": self.accent_on,
|
|
213
|
+
f"{prefix}-overlay": self.overlay,
|
|
214
|
+
f"{prefix}-shadow": self.shadow,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
def tokens(self) -> dict[str, str]:
|
|
218
|
+
"""Return the semantic color tokens for UI usage."""
|
|
219
|
+
return {
|
|
220
|
+
"bg_primary": self.bg_primary,
|
|
221
|
+
"bg_secondary": self.bg_secondary,
|
|
222
|
+
"surface": self.surface,
|
|
223
|
+
"surface_hover": self.surface_hover,
|
|
224
|
+
"surface_selected": self.surface_selected,
|
|
225
|
+
"surface_elevated": self.surface_elevated,
|
|
226
|
+
"surface_pressed": self.surface_pressed,
|
|
227
|
+
"surface_disabled": self.surface_disabled,
|
|
228
|
+
"text_primary": self.text_primary,
|
|
229
|
+
"text_secondary": self.text_secondary,
|
|
230
|
+
"text_muted": self.text_muted,
|
|
231
|
+
"text_disabled": self.text_disabled,
|
|
232
|
+
"border": self.border,
|
|
233
|
+
"border_soft": self.border_soft,
|
|
234
|
+
"focus": self.focus,
|
|
235
|
+
"accent_primary": self.accent_primary,
|
|
236
|
+
"accent_alt": self.accent_alt or self.accent_primary,
|
|
237
|
+
"accent_soft": self.accent_soft,
|
|
238
|
+
"accent_on": self.accent_on,
|
|
239
|
+
"overlay": self.overlay,
|
|
240
|
+
"shadow": self.shadow,
|
|
241
|
+
"is_dark": str(self.is_dark).lower(),
|
|
242
|
+
"theme_mode": self.theme_mode.name,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
COLOR_THEMES = [
|
|
247
|
+
DEx_ColorTheme(
|
|
248
|
+
key="minimalismo_corporativo",
|
|
249
|
+
name="Minimalismo Corporativo",
|
|
250
|
+
description="Clean admin and documentation interfaces with maximum reading comfort.",
|
|
251
|
+
background="#FFFFFF",
|
|
252
|
+
text="#111827",
|
|
253
|
+
accent="#0071E3",
|
|
254
|
+
contrast_ratio=21.0,
|
|
255
|
+
accessibility="AAA",
|
|
256
|
+
),
|
|
257
|
+
DEx_ColorTheme(
|
|
258
|
+
key="dark_performance",
|
|
259
|
+
name="Dark Mode de Alta Performance",
|
|
260
|
+
description="High-contrast dark dashboards for monitoring and performance tools.",
|
|
261
|
+
background="#111111",
|
|
262
|
+
text="#FFFFFF",
|
|
263
|
+
accent="#FF6A13",
|
|
264
|
+
contrast_ratio=18.88,
|
|
265
|
+
accessibility="AAA",
|
|
266
|
+
),
|
|
267
|
+
DEx_ColorTheme(
|
|
268
|
+
key="fintech_trust",
|
|
269
|
+
name="Confianca e Fintech",
|
|
270
|
+
description="Structured B2B and finance interfaces with a secure deep-blue base.",
|
|
271
|
+
background="#141432",
|
|
272
|
+
text="#F4F5F7",
|
|
273
|
+
accent="#0079C1",
|
|
274
|
+
contrast_ratio=16.37,
|
|
275
|
+
accessibility="AAA",
|
|
276
|
+
),
|
|
277
|
+
DEx_ColorTheme(
|
|
278
|
+
key="organic_wellness",
|
|
279
|
+
name="Organico e Bem-Estar",
|
|
280
|
+
description="Calm ecological interfaces for lifestyle, sustainability, and health contexts.",
|
|
281
|
+
background="#006241",
|
|
282
|
+
text="#FFFFFF",
|
|
283
|
+
accent="#00A86B",
|
|
284
|
+
contrast_ratio=7.44,
|
|
285
|
+
accessibility="AAA",
|
|
286
|
+
),
|
|
287
|
+
DEx_ColorTheme(
|
|
288
|
+
key="food_delivery",
|
|
289
|
+
name="Alimentacao e Food Delivery",
|
|
290
|
+
description="Restaurant, POS, order management, and digital menu interfaces.",
|
|
291
|
+
background="#FAFAFA",
|
|
292
|
+
text="#222222",
|
|
293
|
+
accent="#EA1D2C",
|
|
294
|
+
contrast_ratio=15.24,
|
|
295
|
+
accessibility="AAA",
|
|
296
|
+
),
|
|
297
|
+
DEx_ColorTheme(
|
|
298
|
+
key="automotive_service",
|
|
299
|
+
name="Area Automotiva",
|
|
300
|
+
description="Service orders, auto parts catalogs, vehicle intake checklists, and workshop dashboards.",
|
|
301
|
+
background="#1E2229",
|
|
302
|
+
text="#F3F4F6",
|
|
303
|
+
accent="#FFB800",
|
|
304
|
+
accent_alt="#FF5500",
|
|
305
|
+
contrast_ratio=14.50,
|
|
306
|
+
accessibility="AAA",
|
|
307
|
+
),
|
|
308
|
+
DEx_ColorTheme(
|
|
309
|
+
key="banking_tradition",
|
|
310
|
+
name="Tradicao e Robustez",
|
|
311
|
+
description="Internet banking, wallets, ATMs, and investment dashboards with a traditional blue/yellow banking base.",
|
|
312
|
+
background="#F4F6F9",
|
|
313
|
+
text="#003399",
|
|
314
|
+
accent="#003399",
|
|
315
|
+
accent_alt="#FFFF00",
|
|
316
|
+
contrast_ratio=10.03,
|
|
317
|
+
accessibility="AAA",
|
|
318
|
+
),
|
|
319
|
+
DEx_ColorTheme(
|
|
320
|
+
key="banking_digital",
|
|
321
|
+
name="Modernidade Digital",
|
|
322
|
+
description="Digital banking and wallet interfaces with a clear, high-contrast purple identity.",
|
|
323
|
+
background="#FFFFFF",
|
|
324
|
+
text="#820AD1",
|
|
325
|
+
accent="#820AD1",
|
|
326
|
+
contrast_ratio=7.21,
|
|
327
|
+
accessibility="AAA",
|
|
328
|
+
),
|
|
329
|
+
DEx_ColorTheme(
|
|
330
|
+
key="banking_dynamic",
|
|
331
|
+
name="Solidez e Dinamismo",
|
|
332
|
+
description="Financial dashboards and transaction flows with off-white base, blue text, and orange action emphasis.",
|
|
333
|
+
background="#FAFAFA",
|
|
334
|
+
text="#0038A8",
|
|
335
|
+
accent="#FF6200",
|
|
336
|
+
contrast_ratio=9.44,
|
|
337
|
+
accessibility="AAA",
|
|
338
|
+
),
|
|
339
|
+
DEx_ColorTheme(
|
|
340
|
+
key="banking_global",
|
|
341
|
+
name="Presenca Global e Energia",
|
|
342
|
+
description="Banking contracts, account flows, and transaction screens with neutral text and precise red emphasis.",
|
|
343
|
+
background="#FFFFFF",
|
|
344
|
+
text="#1C1C1C",
|
|
345
|
+
accent="#EC0000",
|
|
346
|
+
contrast_ratio=17.04,
|
|
347
|
+
accessibility="AAA",
|
|
348
|
+
),
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def get_color_theme(key: str) -> DEx_ColorTheme:
|
|
353
|
+
"""Return a DEx color theme by key."""
|
|
354
|
+
for theme in COLOR_THEMES:
|
|
355
|
+
if theme.key == key:
|
|
356
|
+
return theme
|
|
357
|
+
raise ValueError(f"Unknown DEx color theme: {key}")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def build_color_theme(theme: DEx_ColorTheme) -> ft.Theme:
|
|
361
|
+
"""Build a Flet theme from a DEx 60-30-10 color theme."""
|
|
362
|
+
return ft.Theme(
|
|
363
|
+
color_scheme_seed=theme.accent_primary,
|
|
364
|
+
color_scheme=ft.ColorScheme(
|
|
365
|
+
primary=theme.accent_primary,
|
|
366
|
+
on_primary=theme.accent_on,
|
|
367
|
+
secondary=theme.accent_primary,
|
|
368
|
+
on_secondary=theme.accent_on,
|
|
369
|
+
surface=theme.surface,
|
|
370
|
+
on_surface=theme.text_primary,
|
|
371
|
+
surface_container_lowest=theme.bg_primary,
|
|
372
|
+
surface_container=theme.surface,
|
|
373
|
+
surface_container_high=theme.surface_elevated,
|
|
374
|
+
),
|
|
375
|
+
)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DEx_Theme.py — Sistema de design do DEx Framework
|
|
3
|
+
Paleta: fundo escuro azulado + violeta elétrico + ciano acento
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import flet as ft
|
|
8
|
+
from components.theme.DEx_ColorThemes import DEx_ColorTheme
|
|
9
|
+
|
|
10
|
+
COMPONENT_VERSION = "0.0.2"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Colors:
|
|
14
|
+
"""Versioned color tokens for the DEx design system."""
|
|
15
|
+
|
|
16
|
+
BG_DEEP = "#0D0F14"
|
|
17
|
+
BG_SURFACE = "#151820"
|
|
18
|
+
BG_ELEVATED = "#1C2030"
|
|
19
|
+
|
|
20
|
+
ACCENT = "#6C63FF"
|
|
21
|
+
ACCENT_DARK = "#4E46CC"
|
|
22
|
+
DATA = "#00E5C3"
|
|
23
|
+
DATA_DARK = "#00B89C"
|
|
24
|
+
|
|
25
|
+
TEXT_PRIMARY = "#F0F2F8"
|
|
26
|
+
TEXT_SECONDARY = "#8B90A8"
|
|
27
|
+
TEXT_MUTED = "#4A4F66"
|
|
28
|
+
TEXT_DISABLED = "#6C738F"
|
|
29
|
+
|
|
30
|
+
BORDER = "#1E2230"
|
|
31
|
+
BORDER_SOFT = "#2A3042"
|
|
32
|
+
BORDER_FOCUS = "#6C63FF"
|
|
33
|
+
|
|
34
|
+
SURFACE_HOVER = "#1D2230"
|
|
35
|
+
SURFACE_SELECTED = "#23293A"
|
|
36
|
+
SURFACE_PRESSED = "#11131A"
|
|
37
|
+
SURFACE_DISABLED = "#10131A"
|
|
38
|
+
OVERLAY = "#00000088"
|
|
39
|
+
|
|
40
|
+
ACCENT_SOFT = "#2B2948"
|
|
41
|
+
ACCENT_ON = "#FFFFFF"
|
|
42
|
+
|
|
43
|
+
SUCCESS = "#22C55E"
|
|
44
|
+
WARNING = "#F59E0B"
|
|
45
|
+
DANGER = "#EF4444"
|
|
46
|
+
INFO = "#3B82F6"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Typography:
|
|
50
|
+
"""Versioned typography tokens for the DEx design system."""
|
|
51
|
+
|
|
52
|
+
DISPLAY = ft.TextStyle(size=32, weight=ft.FontWeight.W_700, color=Colors.TEXT_PRIMARY)
|
|
53
|
+
HEADING_1 = ft.TextStyle(size=24, weight=ft.FontWeight.W_600, color=Colors.TEXT_PRIMARY)
|
|
54
|
+
HEADING_2 = ft.TextStyle(size=18, weight=ft.FontWeight.W_600, color=Colors.TEXT_PRIMARY)
|
|
55
|
+
HEADING_3 = ft.TextStyle(size=14, weight=ft.FontWeight.W_600, color=Colors.TEXT_SECONDARY, letter_spacing=1.2)
|
|
56
|
+
BODY = ft.TextStyle(size=14, weight=ft.FontWeight.NORMAL, color=Colors.TEXT_PRIMARY)
|
|
57
|
+
CAPTION = ft.TextStyle(size=12, weight=ft.FontWeight.NORMAL, color=Colors.TEXT_SECONDARY)
|
|
58
|
+
METRIC = ft.TextStyle(size=28, weight=ft.FontWeight.W_700, color=Colors.TEXT_PRIMARY)
|
|
59
|
+
CODE = ft.TextStyle(size=13, font_family="monospace", color=Colors.DATA)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Spacing:
|
|
63
|
+
"""Versioned spacing and sizing tokens for the DEx design system."""
|
|
64
|
+
|
|
65
|
+
XS = 4
|
|
66
|
+
SM = 8
|
|
67
|
+
MD = 16
|
|
68
|
+
LG = 24
|
|
69
|
+
XL = 32
|
|
70
|
+
XXL = 48
|
|
71
|
+
|
|
72
|
+
SIDEBAR_WIDTH = 240
|
|
73
|
+
HEADER_HEIGHT = 60
|
|
74
|
+
CARD_BORDER_RADIUS = 12
|
|
75
|
+
BUTTON_BORDER_RADIUS = 8
|
|
76
|
+
INPUT_BORDER_RADIUS = 10
|
|
77
|
+
PANEL_BORDER_RADIUS = 16
|
|
78
|
+
CHIP_BORDER_RADIUS = 999
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _build_default_theme() -> ft.Theme:
|
|
82
|
+
return ft.Theme(
|
|
83
|
+
color_scheme_seed=Colors.ACCENT,
|
|
84
|
+
color_scheme=ft.ColorScheme(
|
|
85
|
+
primary=Colors.ACCENT,
|
|
86
|
+
on_primary=Colors.TEXT_PRIMARY,
|
|
87
|
+
secondary=Colors.DATA,
|
|
88
|
+
on_secondary=Colors.ACCENT_ON,
|
|
89
|
+
surface=Colors.BG_SURFACE,
|
|
90
|
+
on_surface=Colors.TEXT_PRIMARY,
|
|
91
|
+
surface_container_lowest=Colors.BG_DEEP,
|
|
92
|
+
surface_container=Colors.BG_SURFACE,
|
|
93
|
+
surface_container_high=Colors.BG_ELEVATED,
|
|
94
|
+
error=Colors.DANGER,
|
|
95
|
+
on_error=Colors.TEXT_PRIMARY,
|
|
96
|
+
),
|
|
97
|
+
text_theme=ft.TextTheme(
|
|
98
|
+
display_large=Typography.DISPLAY,
|
|
99
|
+
headline_medium=Typography.HEADING_1,
|
|
100
|
+
title_medium=Typography.HEADING_2,
|
|
101
|
+
body_medium=Typography.BODY,
|
|
102
|
+
label_small=Typography.CAPTION,
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_theme() -> ft.Theme:
|
|
108
|
+
"""Build the versioned Flet theme from DEx design tokens."""
|
|
109
|
+
return _build_default_theme()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def build_theme_from_color_theme(theme: DEx_ColorTheme) -> ft.Theme:
|
|
113
|
+
"""Build a Flet theme from a DEx color preset."""
|
|
114
|
+
return ft.Theme(
|
|
115
|
+
color_scheme_seed=theme.accent_primary,
|
|
116
|
+
color_scheme=ft.ColorScheme(
|
|
117
|
+
primary=theme.accent_primary,
|
|
118
|
+
on_primary=theme.accent_on,
|
|
119
|
+
secondary=theme.accent_primary,
|
|
120
|
+
on_secondary=theme.accent_on,
|
|
121
|
+
surface=theme.surface,
|
|
122
|
+
on_surface=theme.text_primary,
|
|
123
|
+
surface_container_lowest=theme.bg_primary,
|
|
124
|
+
surface_container=theme.surface,
|
|
125
|
+
surface_container_high=theme.surface_elevated,
|
|
126
|
+
error=Colors.DANGER,
|
|
127
|
+
on_error=Colors.TEXT_PRIMARY,
|
|
128
|
+
),
|
|
129
|
+
text_theme=ft.TextTheme(
|
|
130
|
+
display_large=Typography.DISPLAY,
|
|
131
|
+
headline_medium=Typography.HEADING_1,
|
|
132
|
+
title_medium=Typography.HEADING_2,
|
|
133
|
+
body_medium=Typography.BODY,
|
|
134
|
+
label_small=Typography.CAPTION,
|
|
135
|
+
),
|
|
136
|
+
)
|
components/versioning.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Version helpers for DEx library, components, and release branches."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
VERSION_PATTERN = re.compile(r"^(?P<version>\d{1,2})\.(?P<release>\d{1,2})\.(?P<bugfix>\d{1,2})$")
|
|
10
|
+
MAX_VERSION_SEGMENT = 99
|
|
11
|
+
SEGMENT_ALIASES = {
|
|
12
|
+
"version": "version",
|
|
13
|
+
"vv": "version",
|
|
14
|
+
"major": "version",
|
|
15
|
+
"release": "release",
|
|
16
|
+
"rr": "release",
|
|
17
|
+
"minor": "release",
|
|
18
|
+
"bugfix": "bugfix",
|
|
19
|
+
"bug": "bugfix",
|
|
20
|
+
"bb": "bugfix",
|
|
21
|
+
"patch": "bugfix",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class ComponentMeta:
|
|
27
|
+
"""Catalog metadata for a versioned DEx component."""
|
|
28
|
+
|
|
29
|
+
name: str
|
|
30
|
+
group: str
|
|
31
|
+
version: str
|
|
32
|
+
description: str
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
validate_version(self.version)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_version(version: str) -> str:
|
|
39
|
+
"""Validate a DEx version in vv.rr.bb format."""
|
|
40
|
+
match = VERSION_PATTERN.match(version)
|
|
41
|
+
if not match:
|
|
42
|
+
raise ValueError("DEx version must use the vv.rr.bb format.")
|
|
43
|
+
|
|
44
|
+
segments = [int(value) for value in match.groups()]
|
|
45
|
+
if any(segment > MAX_VERSION_SEGMENT for segment in segments):
|
|
46
|
+
raise ValueError("DEx version segments cannot exceed 99.")
|
|
47
|
+
|
|
48
|
+
return version
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def validate_branch_version(branch_name: str) -> str:
|
|
52
|
+
"""Validate a release branch name using the same vv.rr.bb version format."""
|
|
53
|
+
return validate_version(branch_name)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def bump_version(version: str, segment: str = "patch") -> str:
|
|
57
|
+
"""Increment one DEx version segment.
|
|
58
|
+
|
|
59
|
+
DEx library, component, and release branch versions use vv.rr.bb:
|
|
60
|
+
- vv/version: large visual, API, or usage changes.
|
|
61
|
+
- rr/release: small scoped changes.
|
|
62
|
+
- bb/bugfix: bug corrections.
|
|
63
|
+
|
|
64
|
+
Segments go from 0 to 99. When rr or bb reaches 99 and is incremented,
|
|
65
|
+
the previous segment is incremented and the current segment returns to 0.
|
|
66
|
+
"""
|
|
67
|
+
validate_version(version)
|
|
68
|
+
normalized_segment = SEGMENT_ALIASES.get(segment)
|
|
69
|
+
if normalized_segment is None:
|
|
70
|
+
raise ValueError("segment must be 'version', 'release', or 'bugfix'.")
|
|
71
|
+
|
|
72
|
+
vv, rr, bb = [int(value) for value in version.split(".")]
|
|
73
|
+
if normalized_segment == "version":
|
|
74
|
+
vv = _increment_root_segment(vv)
|
|
75
|
+
rr = 0
|
|
76
|
+
bb = 0
|
|
77
|
+
elif normalized_segment == "release":
|
|
78
|
+
vv, rr = _increment_with_carry(vv, rr)
|
|
79
|
+
bb = 0
|
|
80
|
+
elif normalized_segment == "bugfix":
|
|
81
|
+
vv, rr, bb = _increment_bugfix(vv, rr, bb)
|
|
82
|
+
else:
|
|
83
|
+
raise ValueError("segment must be 'version', 'release', or 'bugfix'.")
|
|
84
|
+
return f"{vv}.{rr}.{bb}"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _increment_root_segment(value: int) -> int:
|
|
88
|
+
if value >= MAX_VERSION_SEGMENT:
|
|
89
|
+
raise ValueError("DEx version cannot exceed 99.99.99.")
|
|
90
|
+
return value + 1
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _increment_with_carry(previous: int, current: int) -> tuple[int, int]:
|
|
94
|
+
if current >= MAX_VERSION_SEGMENT:
|
|
95
|
+
return _increment_root_segment(previous), 0
|
|
96
|
+
return previous, current + 1
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _increment_bugfix(vv: int, rr: int, bb: int) -> tuple[int, int, int]:
|
|
100
|
+
if bb >= MAX_VERSION_SEGMENT:
|
|
101
|
+
vv, rr = _increment_with_carry(vv, rr)
|
|
102
|
+
return vv, rr, 0
|
|
103
|
+
return vv, rr, bb + 1
|