eidosui 0.2.0__py3-none-any.whl → 0.3.0__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.
- eidos/__init__.py +0 -2
- eidos/components/headers.py +45 -6
- eidos/components/navigation.py +78 -0
- eidos/css/styles.css +137 -44
- eidos/js/eidos.js +112 -0
- eidos/plugins/__init__.py +1 -0
- eidos/plugins/markdown/__init__.py +21 -0
- eidos/plugins/markdown/components.py +53 -0
- eidos/plugins/markdown/css/markdown.css +283 -0
- eidos/plugins/markdown/extensions/__init__.py +1 -0
- eidos/plugins/markdown/extensions/alerts.py +134 -0
- eidos/plugins/markdown/renderer.py +58 -0
- eidos/tags.py +63 -63
- eidos/utils.py +16 -7
- {eidosui-0.2.0.dist-info → eidosui-0.3.0.dist-info}/METADATA +8 -5
- eidosui-0.3.0.dist-info/RECORD +22 -0
- eidosui-0.2.0.dist-info/RECORD +0 -13
- {eidosui-0.2.0.dist-info → eidosui-0.3.0.dist-info}/WHEEL +0 -0
- {eidosui-0.2.0.dist-info → eidosui-0.3.0.dist-info}/licenses/LICENSE +0 -0
eidos/__init__.py
CHANGED
eidos/components/headers.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
from air import Meta, Script, Link
|
1
|
+
from air import Meta, Script, Link
|
2
2
|
from ..tags import Body
|
3
|
+
from typing import Literal
|
3
4
|
|
4
5
|
def get_css_urls():
|
5
6
|
"""Return list of CSS URLs for EidosUI."""
|
@@ -9,21 +10,59 @@ def get_css_urls():
|
|
9
10
|
"/eidos/css/themes/light.css",
|
10
11
|
"/eidos/css/themes/dark.css"
|
11
12
|
]
|
12
|
-
|
13
|
-
|
13
|
+
|
14
|
+
def EidosHeaders(
|
15
|
+
include_tailwind: bool = True,
|
16
|
+
include_lucide: bool = True,
|
17
|
+
include_eidos_js: bool = True,
|
18
|
+
theme: Literal["light", "dark"] = "light",
|
19
|
+
):
|
20
|
+
"""Complete EidosUI headers with EidosUI JavaScript support.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
include_tailwind: Include Tailwind CSS CDN
|
24
|
+
include_lucide: Include Lucide Icons CDN
|
25
|
+
include_eidos_js: Include EidosUI JavaScript (navigation, future features)
|
26
|
+
theme: Initial theme
|
27
|
+
"""
|
14
28
|
headers = [
|
15
29
|
Meta(charset="UTF-8"),
|
16
30
|
Meta(name="viewport", content="width=device-width, initial-scale=1.0"),
|
17
31
|
]
|
18
32
|
|
33
|
+
# Core libraries
|
19
34
|
if include_tailwind:
|
20
35
|
headers.append(Script(src="https://cdn.tailwindcss.com"))
|
21
36
|
|
22
|
-
|
37
|
+
if include_lucide:
|
38
|
+
headers.append(Script(src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"))
|
39
|
+
|
40
|
+
# EidosUI CSS
|
23
41
|
for css_url in get_css_urls():
|
24
42
|
headers.append(Link(rel="stylesheet", href=css_url))
|
25
43
|
|
26
|
-
#
|
27
|
-
|
44
|
+
# EidosUI JavaScript
|
45
|
+
if include_eidos_js:
|
46
|
+
headers.append(Script(src="/eidos/js/eidos.js", defer=True))
|
47
|
+
|
48
|
+
# Initialization script
|
49
|
+
init_script = f"""
|
50
|
+
// Set theme
|
51
|
+
document.documentElement.setAttribute('data-theme', '{theme}');
|
52
|
+
"""
|
53
|
+
|
54
|
+
if include_lucide:
|
55
|
+
init_script += """
|
56
|
+
// Initialize Lucide icons
|
57
|
+
if (document.readyState === 'loading') {
|
58
|
+
document.addEventListener('DOMContentLoaded', () => {
|
59
|
+
if (window.lucide) lucide.createIcons();
|
60
|
+
});
|
61
|
+
} else {
|
62
|
+
if (window.lucide) lucide.createIcons();
|
63
|
+
}
|
64
|
+
"""
|
65
|
+
|
66
|
+
headers.append(Script(init_script))
|
28
67
|
|
29
68
|
return headers
|
@@ -0,0 +1,78 @@
|
|
1
|
+
from air import Div, A, I, Tag
|
2
|
+
from ..tags import *
|
3
|
+
from ..utils import stringify
|
4
|
+
from typing import Final, Optional, Any, Union
|
5
|
+
from uuid import uuid4
|
6
|
+
|
7
|
+
class ScrollspyT:
|
8
|
+
underline: Final[str] = 'navbar-underline'
|
9
|
+
bold: Final[str] = 'navbar-bold'
|
10
|
+
|
11
|
+
def NavBar(*c: Any,
|
12
|
+
lcontents: Tag = H3("Title"),
|
13
|
+
right_cls: str = 'items-center space-x-4',
|
14
|
+
mobile_cls: str = '',
|
15
|
+
sticky: bool = False,
|
16
|
+
scrollspy: bool = False,
|
17
|
+
cls: str = 'p-4',
|
18
|
+
scrollspy_cls: str = ScrollspyT.underline,
|
19
|
+
menu_id: Optional[str] = None,
|
20
|
+
) -> Tag:
|
21
|
+
"""Pure Tailwind responsive navigation bar with optional scrollspy.
|
22
|
+
|
23
|
+
Mobile menu uses best practice dropdown with:
|
24
|
+
- Centered text links
|
25
|
+
- Large touch targets
|
26
|
+
- Auto-close on selection
|
27
|
+
- Smooth animations
|
28
|
+
"""
|
29
|
+
if menu_id is None: menu_id = f"menu-{uuid4().hex[:8]}"
|
30
|
+
|
31
|
+
sticky_cls = 'sticky top-0 eidos-navbar-sticky z-50' if sticky else ''
|
32
|
+
|
33
|
+
# Mobile toggle button with hamburger/close icon
|
34
|
+
mobile_icon = A(
|
35
|
+
I(data_lucide="menu", class_="w-6 h-6", data_menu_icon="open"),
|
36
|
+
I(data_lucide="x", class_="w-6 h-6 hidden", data_menu_icon="close"),
|
37
|
+
class_="md:hidden cursor-pointer p-2 eidos-navbar-toggle rounded-lg transition-colors",
|
38
|
+
data_toggle=f"#{menu_id}",
|
39
|
+
role="button",
|
40
|
+
aria_label="Toggle navigation",
|
41
|
+
aria_expanded="false"
|
42
|
+
)
|
43
|
+
|
44
|
+
# Desktop navigation
|
45
|
+
desktop_nav = Div(
|
46
|
+
*c,
|
47
|
+
class_=stringify(right_cls, 'hidden md:flex'),
|
48
|
+
data_scrollspy="true" if scrollspy else None
|
49
|
+
)
|
50
|
+
|
51
|
+
# Mobile navigation
|
52
|
+
mobile_nav = Div(
|
53
|
+
*c,
|
54
|
+
class_=stringify(
|
55
|
+
mobile_cls,
|
56
|
+
'hidden md:hidden absolute top-full left-0 right-0 eidos-navbar-mobile shadow-lg border-t',
|
57
|
+
'flex flex-col eidos-navbar-mobile-divider' if not mobile_cls else '',
|
58
|
+
scrollspy_cls
|
59
|
+
),
|
60
|
+
id=menu_id,
|
61
|
+
data_scrollspy="true" if scrollspy else None,
|
62
|
+
data_mobile_menu="true"
|
63
|
+
)
|
64
|
+
|
65
|
+
return Div(
|
66
|
+
# Main navbar container with relative positioning for mobile dropdown
|
67
|
+
Div(
|
68
|
+
Div(
|
69
|
+
lcontents,
|
70
|
+
mobile_icon,
|
71
|
+
desktop_nav,
|
72
|
+
class_='flex items-center justify-between'
|
73
|
+
),
|
74
|
+
mobile_nav,
|
75
|
+
class_=stringify('eidos-navbar relative', cls, scrollspy_cls)
|
76
|
+
),
|
77
|
+
class_=sticky_cls
|
78
|
+
)
|
eidos/css/styles.css
CHANGED
@@ -27,76 +27,79 @@
|
|
27
27
|
transform: scale(0.98);
|
28
28
|
}
|
29
29
|
|
30
|
-
/*
|
31
|
-
.eidos-btn-primary
|
32
|
-
|
33
|
-
|
30
|
+
/* Button Variants - Using CSS Custom Properties to reduce redundancy */
|
31
|
+
.eidos-btn-primary,
|
32
|
+
.eidos-btn-secondary,
|
33
|
+
.eidos-btn-ghost,
|
34
|
+
.eidos-btn-outline,
|
35
|
+
.eidos-btn-success,
|
36
|
+
.eidos-btn-error,
|
37
|
+
.eidos-btn-cta {
|
38
|
+
background-color: var(--btn-bg, transparent);
|
39
|
+
color: var(--btn-color);
|
40
|
+
border: var(--btn-border, none);
|
34
41
|
}
|
35
42
|
|
36
|
-
.eidos-btn-primary:hover
|
37
|
-
|
43
|
+
.eidos-btn-primary:hover,
|
44
|
+
.eidos-btn-secondary:hover,
|
45
|
+
.eidos-btn-ghost:hover,
|
46
|
+
.eidos-btn-outline:hover,
|
47
|
+
.eidos-btn-success:hover,
|
48
|
+
.eidos-btn-error:hover,
|
49
|
+
.eidos-btn-cta:hover {
|
50
|
+
background-color: var(--btn-bg-hover);
|
51
|
+
color: var(--btn-color-hover, var(--btn-color));
|
38
52
|
}
|
39
53
|
|
40
|
-
/*
|
41
|
-
.eidos-btn-
|
42
|
-
|
43
|
-
color: var(--color-
|
54
|
+
/* Primary Button */
|
55
|
+
.eidos-btn-primary {
|
56
|
+
--btn-bg: var(--color-primary);
|
57
|
+
--btn-color: var(--color-primary-foreground);
|
58
|
+
--btn-bg-hover: var(--color-primary-hover);
|
44
59
|
}
|
45
60
|
|
46
|
-
|
47
|
-
|
61
|
+
/* Secondary Button */
|
62
|
+
.eidos-btn-secondary {
|
63
|
+
--btn-bg: var(--color-secondary-light);
|
64
|
+
--btn-color: var(--color-secondary-dark);
|
65
|
+
--btn-bg-hover: var(--color-border);
|
48
66
|
}
|
49
67
|
|
50
68
|
/* Ghost Button */
|
51
69
|
.eidos-btn-ghost {
|
52
|
-
|
53
|
-
color: var(--color-secondary-dark);
|
54
|
-
|
55
|
-
|
56
|
-
.eidos-btn-ghost:hover {
|
57
|
-
background-color: rgba(0, 0, 0, var(--opacity-5));
|
70
|
+
--btn-bg: transparent;
|
71
|
+
--btn-color: var(--color-secondary-dark);
|
72
|
+
--btn-bg-hover: rgba(0, 0, 0, var(--opacity-5));
|
58
73
|
}
|
59
74
|
|
60
75
|
/* Outline Button */
|
61
76
|
.eidos-btn-outline {
|
62
|
-
|
63
|
-
border: var(--border-2) solid var(--color-primary);
|
64
|
-
color: var(--color-primary);
|
65
|
-
|
66
|
-
|
67
|
-
.eidos-btn-outline:hover {
|
68
|
-
background-color: var(--color-primary);
|
69
|
-
color: var(--color-primary-foreground);
|
77
|
+
--btn-bg: transparent;
|
78
|
+
--btn-border: var(--border-2) solid var(--color-primary);
|
79
|
+
--btn-color: var(--color-primary);
|
80
|
+
--btn-bg-hover: var(--color-primary);
|
81
|
+
--btn-color-hover: var(--color-primary-foreground);
|
70
82
|
}
|
71
83
|
|
72
84
|
/* Success Button */
|
73
85
|
.eidos-btn-success {
|
74
|
-
|
75
|
-
color: var(--color-success-foreground);
|
76
|
-
|
77
|
-
|
78
|
-
.eidos-btn-success:hover {
|
79
|
-
background-color: var(--color-success-hover);
|
86
|
+
--btn-bg: var(--color-success);
|
87
|
+
--btn-color: var(--color-success-foreground);
|
88
|
+
--btn-bg-hover: var(--color-success-hover);
|
80
89
|
}
|
81
90
|
|
82
91
|
/* Error Button */
|
83
92
|
.eidos-btn-error {
|
84
|
-
|
85
|
-
color: var(--color-error-foreground);
|
86
|
-
|
87
|
-
|
88
|
-
.eidos-btn-error:hover {
|
89
|
-
background-color: var(--color-error-hover);
|
93
|
+
--btn-bg: var(--color-error);
|
94
|
+
--btn-color: var(--color-error-foreground);
|
95
|
+
--btn-bg-hover: var(--color-error-hover);
|
90
96
|
}
|
91
97
|
|
92
98
|
/* CTA Button */
|
93
99
|
.eidos-btn-cta {
|
94
|
-
|
95
|
-
color: var(--color-cta-foreground);
|
96
|
-
|
97
|
-
|
98
|
-
.eidos-btn-cta:hover {
|
99
|
-
background-color: var(--color-cta-hover);
|
100
|
+
--btn-bg: var(--color-cta);
|
101
|
+
--btn-color: var(--color-cta-foreground);
|
102
|
+
--btn-bg-hover: var(--color-cta-hover);
|
100
103
|
}
|
101
104
|
|
102
105
|
.eidos-h1 {
|
@@ -335,3 +338,93 @@
|
|
335
338
|
border-top: var(--border) solid var(--color-border);
|
336
339
|
margin: var(--space-xl) 0;
|
337
340
|
}
|
341
|
+
|
342
|
+
/* Navigation base styles */
|
343
|
+
.eidos-navbar {
|
344
|
+
width: 100%;
|
345
|
+
}
|
346
|
+
|
347
|
+
.eidos-navbar a {
|
348
|
+
padding: 0.5rem 0.75rem;
|
349
|
+
border-radius: 0.375rem;
|
350
|
+
font-size: 0.875rem;
|
351
|
+
font-weight: 500;
|
352
|
+
transition: all 0.2s ease;
|
353
|
+
position: relative;
|
354
|
+
}
|
355
|
+
|
356
|
+
.eidos-navbar a:hover {
|
357
|
+
background-color: var(--color-border);
|
358
|
+
}
|
359
|
+
|
360
|
+
/* Sticky navbar */
|
361
|
+
.eidos-navbar-sticky {
|
362
|
+
background-color: var(--color-background);
|
363
|
+
box-shadow: var(--shadow-sm);
|
364
|
+
}
|
365
|
+
|
366
|
+
/* Mobile toggle button */
|
367
|
+
.eidos-navbar-toggle:hover {
|
368
|
+
background-color: var(--color-border);
|
369
|
+
}
|
370
|
+
|
371
|
+
/* Mobile menu dropdown */
|
372
|
+
.eidos-navbar-mobile {
|
373
|
+
background-color: var(--color-background);
|
374
|
+
border-color: var(--color-border);
|
375
|
+
}
|
376
|
+
|
377
|
+
/* Mobile menu dividers */
|
378
|
+
.eidos-navbar-mobile-divider > a:not(:last-child) {
|
379
|
+
border-bottom: var(--border) solid var(--color-border);
|
380
|
+
}
|
381
|
+
|
382
|
+
/* Underline style */
|
383
|
+
.navbar-underline a.eidos-active::after {
|
384
|
+
content: '';
|
385
|
+
position: absolute;
|
386
|
+
bottom: -2px;
|
387
|
+
left: 0;
|
388
|
+
right: 0;
|
389
|
+
height: 2px;
|
390
|
+
background-color: var(--color-primary);
|
391
|
+
}
|
392
|
+
|
393
|
+
/* Bold style */
|
394
|
+
.navbar-bold a.eidos-active {
|
395
|
+
font-weight: 700;
|
396
|
+
color: var(--color-primary);
|
397
|
+
}
|
398
|
+
|
399
|
+
/* Mobile menu animation */
|
400
|
+
[data-toggle] + * {
|
401
|
+
transition: all 0.3s ease;
|
402
|
+
}
|
403
|
+
|
404
|
+
/* Mobile navigation styles */
|
405
|
+
[data-mobile-menu="true"] {
|
406
|
+
animation: slideDown 0.2s ease-out;
|
407
|
+
}
|
408
|
+
|
409
|
+
[data-mobile-menu="true"] a {
|
410
|
+
padding: 1rem 1.5rem;
|
411
|
+
text-align: center;
|
412
|
+
display: block;
|
413
|
+
font-weight: 500;
|
414
|
+
transition: all 0.2s ease;
|
415
|
+
}
|
416
|
+
|
417
|
+
[data-mobile-menu="true"] a:hover {
|
418
|
+
background-color: var(--color-border);
|
419
|
+
}
|
420
|
+
|
421
|
+
@keyframes slideDown {
|
422
|
+
from {
|
423
|
+
transform: translateY(-0.5rem);
|
424
|
+
opacity: 0;
|
425
|
+
}
|
426
|
+
to {
|
427
|
+
transform: translateY(0);
|
428
|
+
opacity: 1;
|
429
|
+
}
|
430
|
+
}
|
eidos/js/eidos.js
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
// eidos.js - Main EidosUI JavaScript file
|
2
|
+
(function() {
|
3
|
+
'use strict';
|
4
|
+
|
5
|
+
class EidosUI {
|
6
|
+
constructor() {
|
7
|
+
this.initToggle();
|
8
|
+
this.initScrollspy();
|
9
|
+
}
|
10
|
+
|
11
|
+
initToggle() {
|
12
|
+
document.querySelectorAll('[data-toggle]').forEach(btn => {
|
13
|
+
btn.addEventListener('click', (e) => {
|
14
|
+
e.preventDefault();
|
15
|
+
const targetId = btn.dataset.toggle;
|
16
|
+
const target = document.querySelector(targetId);
|
17
|
+
if (target) {
|
18
|
+
const isHidden = target.classList.contains('hidden');
|
19
|
+
target.classList.toggle('hidden');
|
20
|
+
|
21
|
+
// Update ARIA attributes
|
22
|
+
const toggleButtons = document.querySelectorAll(`[data-toggle="${targetId}"][role="button"]`);
|
23
|
+
toggleButtons.forEach(toggleBtn => {
|
24
|
+
toggleBtn.setAttribute('aria-expanded', isHidden);
|
25
|
+
|
26
|
+
// Toggle menu icons if they exist
|
27
|
+
const openIcon = toggleBtn.querySelector('[data-menu-icon="open"]');
|
28
|
+
const closeIcon = toggleBtn.querySelector('[data-menu-icon="close"]');
|
29
|
+
if (openIcon && closeIcon) {
|
30
|
+
openIcon.classList.toggle('hidden');
|
31
|
+
closeIcon.classList.toggle('hidden');
|
32
|
+
}
|
33
|
+
});
|
34
|
+
}
|
35
|
+
});
|
36
|
+
});
|
37
|
+
|
38
|
+
// Auto-close mobile menu when clicking a link
|
39
|
+
document.querySelectorAll('[data-mobile-menu="true"] a').forEach(link => {
|
40
|
+
link.addEventListener('click', () => {
|
41
|
+
const menu = link.closest('[data-mobile-menu="true"]');
|
42
|
+
if (menu && !menu.classList.contains('hidden')) {
|
43
|
+
menu.classList.add('hidden');
|
44
|
+
// Update toggle button state
|
45
|
+
const menuId = '#' + menu.id;
|
46
|
+
const toggleBtn = document.querySelector(`[data-toggle="${menuId}"][role="button"]`);
|
47
|
+
if (toggleBtn) {
|
48
|
+
toggleBtn.setAttribute('aria-expanded', 'false');
|
49
|
+
const openIcon = toggleBtn.querySelector('[data-menu-icon="open"]');
|
50
|
+
const closeIcon = toggleBtn.querySelector('[data-menu-icon="close"]');
|
51
|
+
if (openIcon && closeIcon) {
|
52
|
+
openIcon.classList.remove('hidden');
|
53
|
+
closeIcon.classList.add('hidden');
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
});
|
58
|
+
});
|
59
|
+
}
|
60
|
+
|
61
|
+
initScrollspy() {
|
62
|
+
const containers = document.querySelectorAll('[data-scrollspy="true"]');
|
63
|
+
if (!containers.length) return;
|
64
|
+
|
65
|
+
const sections = document.querySelectorAll('section[id], [data-scrollspy-target]');
|
66
|
+
if (!sections.length) return;
|
67
|
+
|
68
|
+
const observerOptions = {
|
69
|
+
rootMargin: '-20% 0px -70% 0px',
|
70
|
+
threshold: [0, 0.1, 0.5, 1]
|
71
|
+
};
|
72
|
+
|
73
|
+
const observer = new IntersectionObserver((entries) => {
|
74
|
+
entries.forEach(entry => {
|
75
|
+
if (entry.intersectionRatio > 0.1) {
|
76
|
+
const id = entry.target.id;
|
77
|
+
containers.forEach(container => {
|
78
|
+
const links = container.querySelectorAll('a[href^="#"]');
|
79
|
+
links.forEach(link => {
|
80
|
+
const isActive = link.getAttribute('href') === `#${id}`;
|
81
|
+
link.classList.toggle('eidos-active', isActive);
|
82
|
+
});
|
83
|
+
});
|
84
|
+
}
|
85
|
+
});
|
86
|
+
}, observerOptions);
|
87
|
+
|
88
|
+
sections.forEach(section => observer.observe(section));
|
89
|
+
|
90
|
+
// Smooth scrolling for nav links
|
91
|
+
containers.forEach(container => {
|
92
|
+
container.querySelectorAll('a[href^="#"]').forEach(link => {
|
93
|
+
link.addEventListener('click', (e) => {
|
94
|
+
const targetId = link.getAttribute('href');
|
95
|
+
const target = document.querySelector(targetId);
|
96
|
+
if (target) {
|
97
|
+
e.preventDefault();
|
98
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
99
|
+
}
|
100
|
+
});
|
101
|
+
});
|
102
|
+
});
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
window.EidosUI = EidosUI;
|
107
|
+
if (document.readyState === 'loading') {
|
108
|
+
document.addEventListener('DOMContentLoaded', () => new EidosUI());
|
109
|
+
} else {
|
110
|
+
new EidosUI();
|
111
|
+
}
|
112
|
+
})();
|
@@ -0,0 +1 @@
|
|
1
|
+
# EidosUI plugins package
|
@@ -0,0 +1,21 @@
|
|
1
|
+
"""EidosUI Markdown Plugin - Theme-aware markdown rendering
|
2
|
+
|
3
|
+
This plugin provides markdown rendering that automatically integrates with
|
4
|
+
EidosUI themes through CSS variables.
|
5
|
+
|
6
|
+
Basic usage:
|
7
|
+
from eidos.plugins.markdown import Markdown, MarkdownCSS
|
8
|
+
|
9
|
+
# In your document head
|
10
|
+
MarkdownCSS()
|
11
|
+
|
12
|
+
# In your content
|
13
|
+
Markdown("# Hello World\\n\\nThis is **markdown**!")
|
14
|
+
"""
|
15
|
+
|
16
|
+
from .components import Markdown, MarkdownCSS
|
17
|
+
from .renderer import MarkdownRenderer
|
18
|
+
|
19
|
+
__all__ = ["Markdown", "MarkdownCSS", "MarkdownRenderer"]
|
20
|
+
|
21
|
+
__version__ = "0.1.0"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
"""Markdown components for EidosUI"""
|
2
|
+
|
3
|
+
import air
|
4
|
+
from typing import Optional
|
5
|
+
from .renderer import MarkdownRenderer
|
6
|
+
|
7
|
+
|
8
|
+
# Global renderer instance for reuse
|
9
|
+
_renderer = MarkdownRenderer()
|
10
|
+
|
11
|
+
|
12
|
+
def Markdown(content: str, class_: Optional[str] = None, **kwargs) -> air.Div:
|
13
|
+
"""Main markdown component that renders markdown content with theme integration.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
content: Markdown text to render
|
17
|
+
class_: Additional CSS classes to apply
|
18
|
+
**kwargs: Additional attributes to pass to the wrapper div
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
air.Div containing the rendered markdown HTML
|
22
|
+
"""
|
23
|
+
# Render the markdown content
|
24
|
+
html_content = _renderer.render(content)
|
25
|
+
|
26
|
+
# Create the div with raw HTML content
|
27
|
+
if class_:
|
28
|
+
return air.Div(
|
29
|
+
air.RawHTML(html_content),
|
30
|
+
class_=class_,
|
31
|
+
**kwargs
|
32
|
+
)
|
33
|
+
else:
|
34
|
+
return air.Div(
|
35
|
+
air.RawHTML(html_content),
|
36
|
+
**kwargs
|
37
|
+
)
|
38
|
+
|
39
|
+
|
40
|
+
def MarkdownCSS() -> air.Link:
|
41
|
+
"""Returns a link tag to include the markdown CSS.
|
42
|
+
|
43
|
+
This should be included in the head of your document to ensure
|
44
|
+
markdown styling is available.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
air.Link element pointing to the markdown CSS file
|
48
|
+
"""
|
49
|
+
return air.Link(
|
50
|
+
rel="stylesheet",
|
51
|
+
href="/eidos/plugins/markdown/css/markdown.css",
|
52
|
+
type="text/css"
|
53
|
+
)
|
@@ -0,0 +1,283 @@
|
|
1
|
+
/* EidosUI Markdown Styles - Theme-aware markdown rendering */
|
2
|
+
|
3
|
+
/* Base container */
|
4
|
+
.eidos-md {
|
5
|
+
color: var(--color-text);
|
6
|
+
line-height: var(--line-height-relaxed);
|
7
|
+
font-size: var(--font-size-base);
|
8
|
+
}
|
9
|
+
|
10
|
+
/* Headings */
|
11
|
+
.eidos-md h1 {
|
12
|
+
font-size: var(--font-size-4xl);
|
13
|
+
font-weight: var(--font-weight-bold);
|
14
|
+
color: var(--color-text);
|
15
|
+
margin-top: var(--space-2xl);
|
16
|
+
margin-bottom: var(--space-lg);
|
17
|
+
line-height: var(--line-height-tight);
|
18
|
+
}
|
19
|
+
|
20
|
+
.eidos-md h2 {
|
21
|
+
font-size: var(--font-size-3xl);
|
22
|
+
font-weight: var(--font-weight-semibold);
|
23
|
+
color: var(--color-text);
|
24
|
+
margin-top: var(--space-xl);
|
25
|
+
margin-bottom: var(--space-md);
|
26
|
+
line-height: var(--line-height-tight);
|
27
|
+
}
|
28
|
+
|
29
|
+
.eidos-md h3 {
|
30
|
+
font-size: var(--font-size-2xl);
|
31
|
+
font-weight: var(--font-weight-semibold);
|
32
|
+
color: var(--color-text);
|
33
|
+
margin-top: var(--space-lg);
|
34
|
+
margin-bottom: var(--space-md);
|
35
|
+
line-height: var(--line-height-snug);
|
36
|
+
}
|
37
|
+
|
38
|
+
.eidos-md h4 {
|
39
|
+
font-size: var(--font-size-xl);
|
40
|
+
font-weight: var(--font-weight-medium);
|
41
|
+
color: var(--color-text);
|
42
|
+
margin-top: var(--space-lg);
|
43
|
+
margin-bottom: var(--space-sm);
|
44
|
+
line-height: var(--line-height-snug);
|
45
|
+
}
|
46
|
+
|
47
|
+
.eidos-md h5 {
|
48
|
+
font-size: var(--font-size-lg);
|
49
|
+
font-weight: var(--font-weight-medium);
|
50
|
+
color: var(--color-text);
|
51
|
+
margin-top: var(--space-md);
|
52
|
+
margin-bottom: var(--space-sm);
|
53
|
+
line-height: var(--line-height-normal);
|
54
|
+
}
|
55
|
+
|
56
|
+
.eidos-md h6 {
|
57
|
+
font-size: var(--font-size-base);
|
58
|
+
font-weight: var(--font-weight-medium);
|
59
|
+
color: var(--color-text);
|
60
|
+
margin-top: var(--space-md);
|
61
|
+
margin-bottom: var(--space-sm);
|
62
|
+
line-height: var(--line-height-normal);
|
63
|
+
}
|
64
|
+
|
65
|
+
/* Paragraphs */
|
66
|
+
.eidos-md p {
|
67
|
+
margin-top: 0;
|
68
|
+
margin-bottom: var(--space-md);
|
69
|
+
}
|
70
|
+
|
71
|
+
/* Links */
|
72
|
+
.eidos-md a {
|
73
|
+
color: var(--color-primary);
|
74
|
+
text-decoration: underline;
|
75
|
+
text-underline-offset: 2px;
|
76
|
+
transition: color var(--transition-fast);
|
77
|
+
}
|
78
|
+
|
79
|
+
.eidos-md a:hover {
|
80
|
+
color: var(--color-primary-hover);
|
81
|
+
}
|
82
|
+
|
83
|
+
/* Lists */
|
84
|
+
.eidos-md ul {
|
85
|
+
margin-top: 0;
|
86
|
+
margin-bottom: var(--space-md);
|
87
|
+
padding-left: var(--space-lg);
|
88
|
+
list-style-type: disc;
|
89
|
+
}
|
90
|
+
|
91
|
+
.eidos-md ol {
|
92
|
+
margin-top: 0;
|
93
|
+
margin-bottom: var(--space-md);
|
94
|
+
padding-left: var(--space-lg);
|
95
|
+
list-style-type: decimal;
|
96
|
+
}
|
97
|
+
|
98
|
+
.eidos-md li {
|
99
|
+
margin-bottom: var(--space-xs);
|
100
|
+
}
|
101
|
+
|
102
|
+
/* Nested lists */
|
103
|
+
.eidos-md ul ul {
|
104
|
+
list-style-type: circle;
|
105
|
+
margin-top: var(--space-xs);
|
106
|
+
margin-bottom: 0;
|
107
|
+
}
|
108
|
+
|
109
|
+
.eidos-md ul ul ul {
|
110
|
+
list-style-type: square;
|
111
|
+
}
|
112
|
+
|
113
|
+
.eidos-md ol ol {
|
114
|
+
list-style-type: lower-alpha;
|
115
|
+
margin-top: var(--space-xs);
|
116
|
+
margin-bottom: 0;
|
117
|
+
}
|
118
|
+
|
119
|
+
.eidos-md ul ol,
|
120
|
+
.eidos-md ol ul {
|
121
|
+
margin-top: var(--space-xs);
|
122
|
+
margin-bottom: 0;
|
123
|
+
}
|
124
|
+
|
125
|
+
/* Code */
|
126
|
+
.eidos-md code {
|
127
|
+
background-color: var(--color-surface);
|
128
|
+
padding: calc(var(--space-xs) / 2) var(--space-xs);
|
129
|
+
border-radius: var(--radius-sm);
|
130
|
+
font-size: var(--font-size-sm);
|
131
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
|
132
|
+
}
|
133
|
+
|
134
|
+
.eidos-md pre {
|
135
|
+
background-color: var(--color-surface);
|
136
|
+
border: var(--border) solid var(--color-border);
|
137
|
+
border-radius: var(--radius-md);
|
138
|
+
padding: var(--space-md);
|
139
|
+
overflow-x: auto;
|
140
|
+
margin-top: 0;
|
141
|
+
margin-bottom: var(--space-md);
|
142
|
+
}
|
143
|
+
|
144
|
+
.eidos-md pre code {
|
145
|
+
background-color: transparent;
|
146
|
+
padding: 0;
|
147
|
+
border-radius: 0;
|
148
|
+
font-size: var(--font-size-sm);
|
149
|
+
}
|
150
|
+
|
151
|
+
/* Blockquotes */
|
152
|
+
.eidos-md blockquote {
|
153
|
+
border-left: var(--border-4) solid var(--color-primary);
|
154
|
+
padding-left: var(--space-md);
|
155
|
+
margin: var(--space-lg) 0;
|
156
|
+
color: var(--color-text-muted);
|
157
|
+
}
|
158
|
+
|
159
|
+
.eidos-md blockquote p:last-child {
|
160
|
+
margin-bottom: 0;
|
161
|
+
}
|
162
|
+
|
163
|
+
/* Tables */
|
164
|
+
.eidos-md table {
|
165
|
+
width: 100%;
|
166
|
+
border-collapse: collapse;
|
167
|
+
margin: var(--space-lg) 0;
|
168
|
+
font-size: var(--font-size-sm);
|
169
|
+
}
|
170
|
+
|
171
|
+
.eidos-md th {
|
172
|
+
background-color: var(--color-surface);
|
173
|
+
font-weight: var(--font-weight-semibold);
|
174
|
+
padding: var(--space-sm) var(--space-md);
|
175
|
+
border-bottom: var(--border-2) solid var(--color-border);
|
176
|
+
text-align: left;
|
177
|
+
}
|
178
|
+
|
179
|
+
.eidos-md td {
|
180
|
+
padding: var(--space-sm) var(--space-md);
|
181
|
+
border-bottom: var(--border) solid var(--color-border);
|
182
|
+
}
|
183
|
+
|
184
|
+
.eidos-md tr:hover {
|
185
|
+
background-color: rgba(0, 0, 0, var(--opacity-5));
|
186
|
+
}
|
187
|
+
|
188
|
+
/* Horizontal rules */
|
189
|
+
.eidos-md hr {
|
190
|
+
border: none;
|
191
|
+
border-top: var(--border) solid var(--color-border);
|
192
|
+
margin: var(--space-xl) 0;
|
193
|
+
}
|
194
|
+
|
195
|
+
/* Images */
|
196
|
+
.eidos-md img {
|
197
|
+
max-width: 100%;
|
198
|
+
height: auto;
|
199
|
+
border-radius: var(--radius-md);
|
200
|
+
margin: var(--space-md) 0;
|
201
|
+
}
|
202
|
+
|
203
|
+
/* Strong and emphasis */
|
204
|
+
.eidos-md strong {
|
205
|
+
font-weight: var(--font-weight-semibold);
|
206
|
+
color: var(--color-text);
|
207
|
+
}
|
208
|
+
|
209
|
+
.eidos-md em {
|
210
|
+
font-style: italic;
|
211
|
+
}
|
212
|
+
|
213
|
+
/* Definition lists */
|
214
|
+
.eidos-md dl {
|
215
|
+
margin: var(--space-md) 0;
|
216
|
+
}
|
217
|
+
|
218
|
+
.eidos-md dt {
|
219
|
+
font-weight: var(--font-weight-semibold);
|
220
|
+
margin-top: var(--space-md);
|
221
|
+
}
|
222
|
+
|
223
|
+
.eidos-md dd {
|
224
|
+
margin-left: var(--space-lg);
|
225
|
+
margin-bottom: var(--space-sm);
|
226
|
+
}
|
227
|
+
|
228
|
+
/* GitHub-style Alerts */
|
229
|
+
.eidos-alert {
|
230
|
+
padding: var(--space-md);
|
231
|
+
border-radius: var(--radius-md);
|
232
|
+
margin: var(--space-lg) 0;
|
233
|
+
border: var(--border) solid;
|
234
|
+
}
|
235
|
+
|
236
|
+
.eidos-alert-header {
|
237
|
+
display: flex;
|
238
|
+
align-items: center;
|
239
|
+
gap: var(--space-sm);
|
240
|
+
margin-bottom: var(--space-sm);
|
241
|
+
font-weight: var(--font-weight-semibold);
|
242
|
+
}
|
243
|
+
|
244
|
+
.eidos-alert-icon {
|
245
|
+
font-size: var(--font-size-lg);
|
246
|
+
}
|
247
|
+
|
248
|
+
.eidos-alert-title {
|
249
|
+
font-size: var(--font-size-base);
|
250
|
+
}
|
251
|
+
|
252
|
+
.eidos-alert-content {
|
253
|
+
margin-left: calc(var(--font-size-lg) + var(--space-sm));
|
254
|
+
}
|
255
|
+
|
256
|
+
.eidos-alert-content > *:last-child {
|
257
|
+
margin-bottom: 0;
|
258
|
+
}
|
259
|
+
|
260
|
+
/* Alert variants */
|
261
|
+
.eidos-alert-info {
|
262
|
+
background-color: var(--color-info-light);
|
263
|
+
border-color: var(--color-info);
|
264
|
+
color: var(--color-info-dark);
|
265
|
+
}
|
266
|
+
|
267
|
+
.eidos-alert-success {
|
268
|
+
background-color: var(--color-success-light);
|
269
|
+
border-color: var(--color-success);
|
270
|
+
color: var(--color-success-dark);
|
271
|
+
}
|
272
|
+
|
273
|
+
.eidos-alert-warning {
|
274
|
+
background-color: var(--color-warning-light);
|
275
|
+
border-color: var(--color-warning);
|
276
|
+
color: var(--color-warning-dark);
|
277
|
+
}
|
278
|
+
|
279
|
+
.eidos-alert-error {
|
280
|
+
background-color: var(--color-error-light);
|
281
|
+
border-color: var(--color-error);
|
282
|
+
color: var(--color-error-dark);
|
283
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
# Markdown plugin extensions
|
@@ -0,0 +1,134 @@
|
|
1
|
+
"""GitHub-style alerts extension for markdown"""
|
2
|
+
|
3
|
+
import re
|
4
|
+
from markdown.extensions import Extension
|
5
|
+
from markdown.blockprocessors import BlockProcessor
|
6
|
+
import xml.etree.ElementTree as etree
|
7
|
+
from xml.etree.ElementTree import SubElement
|
8
|
+
|
9
|
+
|
10
|
+
class AlertBlockProcessor(BlockProcessor):
|
11
|
+
"""Process GitHub-style alert blocks"""
|
12
|
+
|
13
|
+
# Pattern to match > [!TYPE] at the start of a blockquote
|
14
|
+
RE_ALERT = re.compile(r'^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]', re.MULTILINE)
|
15
|
+
|
16
|
+
# Alert type configurations
|
17
|
+
ALERT_TYPES = {
|
18
|
+
'NOTE': {
|
19
|
+
'class': 'eidos-alert eidos-alert-info',
|
20
|
+
'icon': 'ℹ️',
|
21
|
+
'title': 'Note'
|
22
|
+
},
|
23
|
+
'TIP': {
|
24
|
+
'class': 'eidos-alert eidos-alert-success',
|
25
|
+
'icon': '💡',
|
26
|
+
'title': 'Tip'
|
27
|
+
},
|
28
|
+
'IMPORTANT': {
|
29
|
+
'class': 'eidos-alert eidos-alert-warning',
|
30
|
+
'icon': '❗',
|
31
|
+
'title': 'Important'
|
32
|
+
},
|
33
|
+
'WARNING': {
|
34
|
+
'class': 'eidos-alert eidos-alert-warning',
|
35
|
+
'icon': '⚠️',
|
36
|
+
'title': 'Warning'
|
37
|
+
},
|
38
|
+
'CAUTION': {
|
39
|
+
'class': 'eidos-alert eidos-alert-error',
|
40
|
+
'icon': '🔴',
|
41
|
+
'title': 'Caution'
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
def test(self, parent, block):
|
46
|
+
"""Test if the block is a GitHub-style alert"""
|
47
|
+
return bool(self.RE_ALERT.match(block))
|
48
|
+
|
49
|
+
def run(self, parent, blocks):
|
50
|
+
"""Process the alert block"""
|
51
|
+
block = blocks.pop(0)
|
52
|
+
|
53
|
+
# Extract alert type
|
54
|
+
match = self.RE_ALERT.match(block)
|
55
|
+
if not match:
|
56
|
+
return False
|
57
|
+
|
58
|
+
alert_type = match.group(1)
|
59
|
+
alert_config = self.ALERT_TYPES.get(alert_type, self.ALERT_TYPES['NOTE'])
|
60
|
+
|
61
|
+
# Create the alert container
|
62
|
+
alert_div = SubElement(parent, 'div')
|
63
|
+
alert_div.set('class', alert_config['class'])
|
64
|
+
|
65
|
+
# Add the header with icon and title
|
66
|
+
header = SubElement(alert_div, 'div')
|
67
|
+
header.set('class', 'eidos-alert-header')
|
68
|
+
|
69
|
+
icon_span = SubElement(header, 'span')
|
70
|
+
icon_span.set('class', 'eidos-alert-icon')
|
71
|
+
icon_span.text = alert_config['icon']
|
72
|
+
|
73
|
+
title_span = SubElement(header, 'span')
|
74
|
+
title_span.set('class', 'eidos-alert-title')
|
75
|
+
title_span.text = alert_config['title']
|
76
|
+
|
77
|
+
# Process the content
|
78
|
+
content_div = SubElement(alert_div, 'div')
|
79
|
+
content_div.set('class', 'eidos-alert-content')
|
80
|
+
|
81
|
+
# Remove the alert marker and process the remaining content
|
82
|
+
content = self.RE_ALERT.sub('', block)
|
83
|
+
|
84
|
+
# Handle multi-line content
|
85
|
+
lines = content.split('\n')
|
86
|
+
processed_lines = []
|
87
|
+
|
88
|
+
for line in lines:
|
89
|
+
# Remove leading '>' from each line
|
90
|
+
if line.startswith('>'):
|
91
|
+
line = line[1:].lstrip()
|
92
|
+
processed_lines.append(line)
|
93
|
+
|
94
|
+
# Join the content and parse it
|
95
|
+
content_text = '\n'.join(processed_lines).strip()
|
96
|
+
|
97
|
+
# Parse the content as markdown
|
98
|
+
if content_text:
|
99
|
+
# Create a temporary element to hold parsed content
|
100
|
+
temp_element = etree.Element('div')
|
101
|
+
self.parser.parseBlocks(temp_element, [content_text])
|
102
|
+
|
103
|
+
# Move all children to our content div
|
104
|
+
for child in temp_element:
|
105
|
+
content_div.append(child)
|
106
|
+
|
107
|
+
# If no children were added, add the text directly
|
108
|
+
if len(content_div) == 0:
|
109
|
+
p = SubElement(content_div, 'p')
|
110
|
+
p.text = content_text
|
111
|
+
|
112
|
+
# Continue processing subsequent blocks that might be part of the alert
|
113
|
+
while blocks and blocks[0].startswith('>'):
|
114
|
+
continuation = blocks.pop(0)
|
115
|
+
# Remove leading '>' and process
|
116
|
+
continuation_text = continuation[1:].lstrip() if continuation.startswith('>') else continuation
|
117
|
+
|
118
|
+
if continuation_text:
|
119
|
+
p = SubElement(content_div, 'p')
|
120
|
+
p.text = continuation_text
|
121
|
+
|
122
|
+
return True
|
123
|
+
|
124
|
+
|
125
|
+
class AlertExtension(Extension):
|
126
|
+
"""Add GitHub-style alerts to markdown"""
|
127
|
+
|
128
|
+
def extendMarkdown(self, md):
|
129
|
+
"""Add the alert processor to the markdown instance"""
|
130
|
+
md.parser.blockprocessors.register(
|
131
|
+
AlertBlockProcessor(md.parser),
|
132
|
+
'github_alerts',
|
133
|
+
175 # Priority - process before blockquote
|
134
|
+
)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
"""Core markdown rendering with theme integration"""
|
2
|
+
|
3
|
+
import markdown
|
4
|
+
from typing import Optional, List, Union
|
5
|
+
from .extensions.alerts import AlertExtension
|
6
|
+
|
7
|
+
|
8
|
+
class MarkdownRenderer:
|
9
|
+
"""Core markdown rendering with theme integration"""
|
10
|
+
|
11
|
+
def __init__(self, extensions: Optional[List[Union[str, markdown.Extension]]] = None):
|
12
|
+
"""Initialize the renderer with optional extensions.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
extensions: List of markdown extension names or instances to enable
|
16
|
+
"""
|
17
|
+
self.extensions = extensions or []
|
18
|
+
# Add some useful default extensions
|
19
|
+
default_extensions = [
|
20
|
+
'fenced_code',
|
21
|
+
'tables',
|
22
|
+
'nl2br',
|
23
|
+
'sane_lists',
|
24
|
+
AlertExtension() # Add GitHub-style alerts
|
25
|
+
]
|
26
|
+
self.extensions.extend(default_extensions)
|
27
|
+
|
28
|
+
# Initialize the markdown processor
|
29
|
+
self.md = markdown.Markdown(extensions=self.extensions)
|
30
|
+
|
31
|
+
def render(self, markdown_text: str) -> str:
|
32
|
+
"""Convert markdown to themed HTML.
|
33
|
+
|
34
|
+
Args:
|
35
|
+
markdown_text: Raw markdown text to render
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
HTML string wrapped with eidos-md class for styling
|
39
|
+
"""
|
40
|
+
# Reset the markdown processor to clear any state
|
41
|
+
self.md.reset()
|
42
|
+
|
43
|
+
# Convert markdown to HTML
|
44
|
+
html_content = self.md.convert(markdown_text)
|
45
|
+
|
46
|
+
# Wrap in a div with our markdown class for styling
|
47
|
+
return f'<div class="eidos-md">{html_content}</div>'
|
48
|
+
|
49
|
+
def add_extension(self, extension: str) -> None:
|
50
|
+
"""Add a markdown extension.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
extension: Name of the markdown extension to add
|
54
|
+
"""
|
55
|
+
if extension not in self.extensions:
|
56
|
+
self.extensions.append(extension)
|
57
|
+
# Recreate the markdown processor with new extensions
|
58
|
+
self.md = markdown.Markdown(extensions=self.extensions)
|
eidos/tags.py
CHANGED
@@ -1,99 +1,99 @@
|
|
1
|
-
from typing import Optional, Literal
|
1
|
+
from typing import Optional, Literal, Any, Union
|
2
2
|
import air
|
3
3
|
from . import styles
|
4
4
|
from .utils import stringify
|
5
5
|
|
6
|
-
def Button(*content,
|
7
|
-
return air.Button(*content,
|
6
|
+
def Button(*content: Any, class_: Optional[Union[str, list[str]]] = styles.buttons.primary, **kwargs: Any) -> air.Tag:
|
7
|
+
return air.Button(*content, class_=stringify(styles.buttons.base, class_), **kwargs)
|
8
8
|
|
9
|
-
def H1(*content,
|
10
|
-
return air.H1(*content,
|
9
|
+
def H1(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
10
|
+
return air.H1(*content, class_=stringify(styles.typography.h1, class_), **kwargs)
|
11
11
|
|
12
|
-
def H2(*content,
|
13
|
-
return air.H2(*content,
|
12
|
+
def H2(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
13
|
+
return air.H2(*content, class_=stringify(styles.typography.h2, class_), **kwargs)
|
14
14
|
|
15
|
-
def H3(*content,
|
16
|
-
return air.H3(*content,
|
15
|
+
def H3(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
16
|
+
return air.H3(*content, class_=stringify(styles.typography.h3, class_), **kwargs)
|
17
17
|
|
18
|
-
def H4(*content,
|
19
|
-
return air.H4(*content,
|
18
|
+
def H4(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
19
|
+
return air.H4(*content, class_=stringify(styles.typography.h4, class_), **kwargs)
|
20
20
|
|
21
|
-
def H5(*content,
|
22
|
-
return air.H5(*content,
|
21
|
+
def H5(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
22
|
+
return air.H5(*content, class_=stringify(styles.typography.h5, class_), **kwargs)
|
23
23
|
|
24
|
-
def H6(*content,
|
25
|
-
return air.H6(*content,
|
24
|
+
def H6(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
25
|
+
return air.H6(*content, class_=stringify(styles.typography.h6, class_), **kwargs)
|
26
26
|
|
27
|
-
def Body(*content,
|
28
|
-
return air.Body(*content,
|
27
|
+
def Body(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
28
|
+
return air.Body(*content, class_=stringify(styles.Theme.body, class_), **kwargs)
|
29
29
|
|
30
30
|
# Semantic HTML Elements
|
31
31
|
|
32
|
-
def Strong(*content,
|
33
|
-
return air.Strong(*content,
|
32
|
+
def Strong(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
33
|
+
return air.Strong(*content, class_=stringify(styles.semantic.strong, class_), **kwargs)
|
34
34
|
|
35
|
-
def I(*content,
|
36
|
-
return air.I(*content,
|
35
|
+
def I(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
36
|
+
return air.I(*content, class_=stringify(styles.semantic.i, class_), **kwargs)
|
37
37
|
|
38
|
-
def Small(*content,
|
39
|
-
return air.Small(*content,
|
38
|
+
def Small(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
39
|
+
return air.Small(*content, class_=stringify(styles.semantic.small, class_), **kwargs)
|
40
40
|
|
41
|
-
def Del(*content,
|
42
|
-
return air.Del(*content,
|
41
|
+
def Del(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
42
|
+
return air.Del(*content, class_=stringify(styles.semantic.del_, class_), **kwargs)
|
43
43
|
|
44
|
-
def Abbr(*content,
|
45
|
-
return air.Abbr(*content,
|
44
|
+
def Abbr(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
45
|
+
return air.Abbr(*content, class_=stringify(styles.semantic.abbr, class_), **kwargs)
|
46
46
|
|
47
|
-
def Var(*content,
|
48
|
-
return air.Var(*content,
|
47
|
+
def Var(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
48
|
+
return air.Var(*content, class_=stringify(styles.semantic.var, class_), **kwargs)
|
49
49
|
|
50
|
-
def Mark(*content,
|
51
|
-
return air.Mark(*content,
|
50
|
+
def Mark(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
51
|
+
return air.Mark(*content, class_=stringify(styles.semantic.mark, class_), **kwargs)
|
52
52
|
|
53
|
-
def Time(*content,
|
54
|
-
return air.Time(*content,
|
53
|
+
def Time(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
54
|
+
return air.Time(*content, class_=stringify(styles.semantic.time, class_), **kwargs)
|
55
55
|
|
56
|
-
def Code(*content,
|
57
|
-
return air.Code(*content,
|
56
|
+
def Code(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
57
|
+
return air.Code(*content, class_=stringify(styles.semantic.code, class_), **kwargs)
|
58
58
|
|
59
|
-
def Pre(*content,
|
60
|
-
return air.Pre(*content,
|
59
|
+
def Pre(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
60
|
+
return air.Pre(*content, class_=stringify(styles.semantic.pre, class_), **kwargs)
|
61
61
|
|
62
|
-
def Kbd(*content,
|
63
|
-
return air.Kbd(*content,
|
62
|
+
def Kbd(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
63
|
+
return air.Kbd(*content, class_=stringify(styles.semantic.kbd, class_), **kwargs)
|
64
64
|
|
65
|
-
def Samp(*content,
|
66
|
-
return air.Samp(*content,
|
65
|
+
def Samp(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
66
|
+
return air.Samp(*content, class_=stringify(styles.semantic.samp, class_), **kwargs)
|
67
67
|
|
68
|
-
def Blockquote(*content,
|
69
|
-
return air.Blockquote(*content,
|
68
|
+
def Blockquote(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
69
|
+
return air.Blockquote(*content, class_=stringify(styles.semantic.blockquote, class_), **kwargs)
|
70
70
|
|
71
|
-
def Cite(*content,
|
72
|
-
return air.Cite(*content,
|
71
|
+
def Cite(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
72
|
+
return air.Cite(*content, class_=stringify(styles.semantic.cite, class_), **kwargs)
|
73
73
|
|
74
|
-
def Address(*content,
|
75
|
-
return air.Address(*content,
|
74
|
+
def Address(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
75
|
+
return air.Address(*content, class_=stringify(styles.semantic.address, class_), **kwargs)
|
76
76
|
|
77
|
-
def Hr(
|
78
|
-
return air.Hr(
|
77
|
+
def Hr(class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
78
|
+
return air.Hr(class_=stringify(styles.semantic.hr, class_), **kwargs)
|
79
79
|
|
80
|
-
def Details(*content,
|
81
|
-
return air.Details(*content,
|
80
|
+
def Details(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
81
|
+
return air.Details(*content, class_=stringify(styles.semantic.details, class_), **kwargs)
|
82
82
|
|
83
|
-
def Summary(*content,
|
84
|
-
return air.Summary(*content,
|
83
|
+
def Summary(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
84
|
+
return air.Summary(*content, class_=stringify(styles.semantic.summary, class_), **kwargs)
|
85
85
|
|
86
|
-
def Dl(*content,
|
87
|
-
return air.Dl(*content,
|
86
|
+
def Dl(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
87
|
+
return air.Dl(*content, class_=stringify(styles.semantic.dl, class_), **kwargs)
|
88
88
|
|
89
|
-
def Dt(*content,
|
90
|
-
return air.Dt(*content,
|
89
|
+
def Dt(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
90
|
+
return air.Dt(*content, class_=stringify(styles.semantic.dt, class_), **kwargs)
|
91
91
|
|
92
|
-
def Dd(*content,
|
93
|
-
return air.Dd(*content,
|
92
|
+
def Dd(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
93
|
+
return air.Dd(*content, class_=stringify(styles.semantic.dd, class_), **kwargs)
|
94
94
|
|
95
|
-
def Figure(*content,
|
96
|
-
return air.Figure(*content,
|
95
|
+
def Figure(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
96
|
+
return air.Figure(*content, class_=stringify(styles.semantic.figure, class_), **kwargs)
|
97
97
|
|
98
|
-
def Figcaption(*content,
|
99
|
-
return air.Figcaption(*content,
|
98
|
+
def Figcaption(*content: Any, class_: Optional[Union[str, list[str]]] = None, **kwargs: Any) -> air.Tag:
|
99
|
+
return air.Figcaption(*content, class_=stringify(styles.semantic.figcaption, class_), **kwargs)
|
eidos/utils.py
CHANGED
@@ -27,14 +27,14 @@ def stringify(*classes: Optional[Union[str, List[str]]]) -> str:
|
|
27
27
|
"""
|
28
28
|
result = []
|
29
29
|
|
30
|
-
for
|
31
|
-
if
|
30
|
+
for class_ in classes:
|
31
|
+
if class_ is None:
|
32
32
|
continue
|
33
|
-
elif isinstance(
|
33
|
+
elif isinstance(class_, list):
|
34
34
|
# Recursively handle lists
|
35
|
-
result.extend(c for c in
|
36
|
-
elif isinstance(
|
37
|
-
result.append(
|
35
|
+
result.extend(c for c in class_ if c)
|
36
|
+
elif isinstance(class_, str) and class_.strip():
|
37
|
+
result.append(class_.strip())
|
38
38
|
|
39
39
|
return " ".join(result)
|
40
40
|
|
@@ -57,7 +57,16 @@ def get_eidos_static_directory() -> str:
|
|
57
57
|
"""
|
58
58
|
try:
|
59
59
|
from importlib.resources import files
|
60
|
-
|
60
|
+
import pathlib
|
61
|
+
# Convert MultiplexedPath to actual filesystem path
|
62
|
+
eidos_path = files('eidos')
|
63
|
+
if hasattr(eidos_path, '_paths'):
|
64
|
+
# MultiplexedPath - get the first valid path
|
65
|
+
for path in eidos_path._paths:
|
66
|
+
if isinstance(path, pathlib.Path) and path.exists():
|
67
|
+
return str(path)
|
68
|
+
# Try to get the path directly
|
69
|
+
return str(eidos_path)
|
61
70
|
except (ImportError, AttributeError):
|
62
71
|
# Fallback for development or if importlib.resources fails
|
63
72
|
return os.path.dirname(os.path.abspath(__file__))
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: eidosui
|
3
|
-
Version: 0.
|
4
|
-
Summary: A modern, Tailwind CSS-based UI library for
|
3
|
+
Version: 0.3.0
|
4
|
+
Summary: A modern, Tailwind CSS-based UI library for air development
|
5
5
|
Project-URL: Homepage, https://github.com/isaac-flath/EidosUI
|
6
6
|
Project-URL: Repository, https://github.com/isaac-flath/EidosUI
|
7
7
|
Project-URL: Issues, https://github.com/isaac-flath/EidosUI/issues
|
@@ -9,7 +9,7 @@ Project-URL: Documentation, https://github.com/isaac-flath/EidosUI#readme
|
|
9
9
|
Author: Isaac Flath
|
10
10
|
License-Expression: MIT
|
11
11
|
License-File: LICENSE
|
12
|
-
Keywords: components,css,fastapi,tailwind,ui,web
|
12
|
+
Keywords: air,components,css,fastapi,tailwind,ui,web
|
13
13
|
Classifier: Development Status :: 3 - Alpha
|
14
14
|
Classifier: Intended Audience :: Developers
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -17,11 +17,12 @@ Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.10
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
20
21
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
21
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
22
23
|
Requires-Python: >=3.10
|
23
|
-
Requires-Dist: air
|
24
|
-
Requires-Dist: fastapi
|
24
|
+
Requires-Dist: air>=0.12
|
25
|
+
Requires-Dist: fastapi[standard]
|
25
26
|
Requires-Dist: uvicorn
|
26
27
|
Provides-Extra: dev
|
27
28
|
Requires-Dist: black; extra == 'dev'
|
@@ -29,6 +30,8 @@ Requires-Dist: isort; extra == 'dev'
|
|
29
30
|
Requires-Dist: mypy; extra == 'dev'
|
30
31
|
Requires-Dist: pytest; extra == 'dev'
|
31
32
|
Requires-Dist: ruff; extra == 'dev'
|
33
|
+
Provides-Extra: markdown
|
34
|
+
Requires-Dist: markdown>=3.4; extra == 'markdown'
|
32
35
|
Description-Content-Type: text/markdown
|
33
36
|
|
34
37
|
# EidosUI 🎨
|
@@ -0,0 +1,22 @@
|
|
1
|
+
eidos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
eidos/styles.py,sha256=QupOwC0VuSZ6zdNJiNFT_PqTnvbJvaNplKO5M5xzMVI,2380
|
3
|
+
eidos/tags.py,sha256=2NidaD6JHDZ9nfSMXFKrG20eNc_u9K0t0t4xPdWJPUE,5966
|
4
|
+
eidos/utils.py,sha256=DUw2a--J2rpZLY7YD3-xtHSmrK0YrnJuPi4rwV41zT8,2345
|
5
|
+
eidos/components/headers.py,sha256=M4SZ_w_ius9cj7uRALo2niAkBz-iLYMVYAX8p556vyo,2005
|
6
|
+
eidos/components/navigation.py,sha256=_8xQxVuZYmOIWk_85pa1L1ECYAXphIxV4iFmolNRwWQ,2515
|
7
|
+
eidos/css/styles.css,sha256=KvrqNpdi7y0felLNhXwgys7cD4mbWjd5EzSqQWxjRgg,9770
|
8
|
+
eidos/css/themes/dark.css,sha256=yUDNz7iLEJ_Q63MrKXlrhYcpojm1L2eNUOthzhYc3Vs,4170
|
9
|
+
eidos/css/themes/eidos-variables.css,sha256=s4_CUivnqvndE4c7T1stp59orEHL_0C9tmN0rxai7bU,5686
|
10
|
+
eidos/css/themes/light.css,sha256=kESbN5Z4ovCn-Q3fxilQcjafgMCIyyJiocU9kMC5sIM,2674
|
11
|
+
eidos/js/eidos.js,sha256=ag6aoyWIE234ZoMiXJV-KVgBTsPR-fZd5Qhuo4rEeh0,5048
|
12
|
+
eidos/plugins/__init__.py,sha256=Cv4nNAV74tOvyPyb2oEg7q2yhrHLC1lGwbWYjdT9Z4Y,25
|
13
|
+
eidos/plugins/markdown/__init__.py,sha256=FuifCP1MmO0ACy7mq_kTwKLMnw8K15td_6E3Ihlrir4,555
|
14
|
+
eidos/plugins/markdown/components.py,sha256=jz3yu4LIgBOTx0MS2o703al90jAquScmKehQQHV2dmY,1388
|
15
|
+
eidos/plugins/markdown/renderer.py,sha256=NJYTJWgDySTUkrihYudLvRSR4hr6QeAqTpzZGN4eyo8,1971
|
16
|
+
eidos/plugins/markdown/css/markdown.css,sha256=RxNx7aJDmJMx3zuydX10uAMaPFtl9r6zOFDni9Idsdk,6226
|
17
|
+
eidos/plugins/markdown/extensions/__init__.py,sha256=wOdOyCcb-4lxEm77GMnJLst8JepecDcrN4sp3HO_q9A,28
|
18
|
+
eidos/plugins/markdown/extensions/alerts.py,sha256=q1d49KyjPoTiK6OJ7gKvuwrFxM3BjPwssQnp6qhpFDs,4541
|
19
|
+
eidosui-0.3.0.dist-info/METADATA,sha256=Dz7d3-Q3dAxHKOpJFD7YivKEukMDM4kQldJGYH5z7kE,4749
|
20
|
+
eidosui-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
21
|
+
eidosui-0.3.0.dist-info/licenses/LICENSE,sha256=evjPJs6lg9eka9CYtC6ErQrZ71IovVbdvZqqz3ax8E8,1064
|
22
|
+
eidosui-0.3.0.dist-info/RECORD,,
|
eidosui-0.2.0.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
eidos/__init__.py,sha256=wPMqILVs6Iof510BteshYDZgsW-08w_RfenImYKlPlM,40
|
2
|
-
eidos/styles.py,sha256=QupOwC0VuSZ6zdNJiNFT_PqTnvbJvaNplKO5M5xzMVI,2380
|
3
|
-
eidos/tags.py,sha256=9jX9u8QLHjv48vRB7yorR3dV25UEt3AjgUlYPJDcxPM,3975
|
4
|
-
eidos/utils.py,sha256=dncLrIjn78x6HW-lX1feG76jtAilfLaxsRmJ8ewCCCs,1922
|
5
|
-
eidos/components/headers.py,sha256=qE5xa2Yt545FGlBAITPjPAU5CQkz0cnYq7biYcb6mB0,939
|
6
|
-
eidos/css/styles.css,sha256=4VWOWe436DQRXBN4b6g6tsIRqXsYJrwuoKNnsrQQv_M,7643
|
7
|
-
eidos/css/themes/dark.css,sha256=yUDNz7iLEJ_Q63MrKXlrhYcpojm1L2eNUOthzhYc3Vs,4170
|
8
|
-
eidos/css/themes/eidos-variables.css,sha256=s4_CUivnqvndE4c7T1stp59orEHL_0C9tmN0rxai7bU,5686
|
9
|
-
eidos/css/themes/light.css,sha256=kESbN5Z4ovCn-Q3fxilQcjafgMCIyyJiocU9kMC5sIM,2674
|
10
|
-
eidosui-0.2.0.dist-info/METADATA,sha256=ME2HeKBZS9nenM2SpdQ5FBYbA51rlU0CnO3UvoDy44g,4609
|
11
|
-
eidosui-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
12
|
-
eidosui-0.2.0.dist-info/licenses/LICENSE,sha256=evjPJs6lg9eka9CYtC6ErQrZ71IovVbdvZqqz3ax8E8,1064
|
13
|
-
eidosui-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|