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.
Files changed (82) hide show
  1. components/__init__.py +119 -0
  2. components/buttons/DEx_BtnFAB.py +37 -0
  3. components/buttons/DEx_BtnFilled.py +20 -0
  4. components/buttons/DEx_BtnGhost.py +20 -0
  5. components/buttons/DEx_BtnIcon.py +18 -0
  6. components/buttons/DEx_BtnPrimary.py +21 -0
  7. components/buttons/DEx_BtnSegmented.py +45 -0
  8. components/buttons/DEx_BtnText.py +19 -0
  9. components/buttons/DEx_BtnTonal.py +20 -0
  10. components/buttons/DEx_MenuBar.py +61 -0
  11. components/buttons/DEx_PopupMenu.py +43 -0
  12. components/buttons/__init__.py +0 -0
  13. components/cards/DEx_Card.py +23 -0
  14. components/cards/DEx_ContactCard.py +52 -0
  15. components/cards/DEx_MetricCard.py +36 -0
  16. components/cards/__init__.py +0 -0
  17. components/data/DEx_DataTable.py +82 -0
  18. components/data/DEx_ExpansionPanel.py +39 -0
  19. components/data/DEx_ExpansionTile.py +29 -0
  20. components/data/DEx_GridView.py +65 -0
  21. components/data/DEx_ListTile.py +27 -0
  22. components/data/DEx_ListView.py +65 -0
  23. components/data/DEx_PipelineColumn.py +74 -0
  24. components/data/DEx_TreeView.py +536 -0
  25. components/data/__init__.py +0 -0
  26. components/feedback/DEx_AlertDialog.py +26 -0
  27. components/feedback/DEx_Banner.py +19 -0
  28. components/feedback/DEx_BottomSheet.py +37 -0
  29. components/feedback/DEx_Notify.py +15 -0
  30. components/feedback/DEx_ProgressBar.py +34 -0
  31. components/feedback/DEx_ProgressRing.py +52 -0
  32. components/feedback/DEx_Shimmer.py +84 -0
  33. components/feedback/DEx_Tooltip.py +24 -0
  34. components/feedback/__init__.py +0 -0
  35. components/inputs/DEx_AutoComplete.py +29 -0
  36. components/inputs/DEx_Checkbox.py +27 -0
  37. components/inputs/DEx_Chip.py +33 -0
  38. components/inputs/DEx_DatePicker.py +72 -0
  39. components/inputs/DEx_Dropdown.py +25 -0
  40. components/inputs/DEx_FilePicker.py +70 -0
  41. components/inputs/DEx_Radio.py +37 -0
  42. components/inputs/DEx_SearchBar.py +37 -0
  43. components/inputs/DEx_SearchField.py +9 -0
  44. components/inputs/DEx_Slider.py +49 -0
  45. components/inputs/DEx_Switch.py +22 -0
  46. components/inputs/DEx_TextField.py +29 -0
  47. components/inputs/DEx_TimePicker.py +71 -0
  48. components/inputs/__init__.py +0 -0
  49. components/layout/DEx_AnimatedSwitcher.py +43 -0
  50. components/layout/DEx_AppBar.py +48 -0
  51. components/layout/DEx_AppShell.py +80 -0
  52. components/layout/DEx_Draggable.py +67 -0
  53. components/layout/DEx_GestureDetector.py +52 -0
  54. components/layout/DEx_Header.py +57 -0
  55. components/layout/DEx_NavItem.py +61 -0
  56. components/layout/DEx_NavigationBar.py +30 -0
  57. components/layout/DEx_NavigationDrawer.py +60 -0
  58. components/layout/DEx_NavigationRail.py +42 -0
  59. components/layout/DEx_ResponsiveRow.py +42 -0
  60. components/layout/DEx_Sidebar.py +131 -0
  61. components/layout/DEx_Stack.py +73 -0
  62. components/layout/DEx_Tabs.py +110 -0
  63. components/layout/__init__.py +0 -0
  64. components/metadata.py +98 -0
  65. components/primitives/DEx_Avatar.py +87 -0
  66. components/primitives/DEx_Badge.py +18 -0
  67. components/primitives/DEx_Divider.py +13 -0
  68. components/primitives/DEx_Icon.py +46 -0
  69. components/primitives/DEx_Image.py +55 -0
  70. components/primitives/DEx_Markdown.py +21 -0
  71. components/primitives/DEx_SectionEyebrow.py +22 -0
  72. components/primitives/DEx_StatusDot.py +8 -0
  73. components/primitives/__init__.py +0 -0
  74. components/project.py +23 -0
  75. components/theme/DEx_ColorThemes.py +375 -0
  76. components/theme/DEx_Theme.py +136 -0
  77. components/theme/__init__.py +4 -0
  78. components/versioning.py +103 -0
  79. saas_framework-0.1.1.dist-info/METADATA +450 -0
  80. saas_framework-0.1.1.dist-info/RECORD +82 -0
  81. saas_framework-0.1.1.dist-info/WHEEL +5 -0
  82. 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
+ )
@@ -0,0 +1,8 @@
1
+ import flet as ft
2
+
3
+ COMPONENT_VERSION = "0.0.1"
4
+
5
+
6
+ def DEx_StatusDot(color: str) -> ft.Container:
7
+ """Create the versioned small colored state dot."""
8
+ return ft.Container(width=8, height=8, bgcolor=color, border_radius=4)
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
+ )
@@ -0,0 +1,4 @@
1
+ """Theme helpers for the DEx Framework."""
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, build_color_theme, get_color_theme
@@ -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