eidosui 0.2.0__tar.gz → 0.4.0__tar.gz
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.
- {eidosui-0.2.0 → eidosui-0.4.0}/.gitignore +1 -1
- {eidosui-0.2.0 → eidosui-0.4.0}/PKG-INFO +8 -5
- eidosui-0.4.0/eidos/__init__.py +0 -0
- eidosui-0.4.0/eidos/components/headers.py +68 -0
- eidosui-0.4.0/eidos/components/navigation.py +78 -0
- {eidosui-0.2.0 → eidosui-0.4.0}/eidos/css/styles.css +137 -44
- eidosui-0.4.0/eidos/js/eidos.js +112 -0
- eidosui-0.4.0/eidos/plugins/__init__.py +1 -0
- eidosui-0.4.0/eidos/plugins/markdown/__init__.py +21 -0
- eidosui-0.4.0/eidos/plugins/markdown/components.py +53 -0
- eidosui-0.4.0/eidos/plugins/markdown/css/markdown.css +283 -0
- eidosui-0.4.0/eidos/plugins/markdown/extensions/__init__.py +1 -0
- eidosui-0.4.0/eidos/plugins/markdown/extensions/alerts.py +134 -0
- eidosui-0.4.0/eidos/plugins/markdown/renderer.py +58 -0
- eidosui-0.4.0/eidos/tags.py +194 -0
- {eidosui-0.2.0 → eidosui-0.4.0}/eidos/utils.py +16 -7
- {eidosui-0.2.0 → eidosui-0.4.0}/pyproject.toml +16 -47
- eidosui-0.2.0/eidos/__init__.py +0 -2
- eidosui-0.2.0/eidos/components/headers.py +0 -29
- eidosui-0.2.0/eidos/tags.py +0 -99
- {eidosui-0.2.0 → eidosui-0.4.0}/LICENSE +0 -0
- {eidosui-0.2.0 → eidosui-0.4.0}/README.md +0 -0
- {eidosui-0.2.0 → eidosui-0.4.0}/eidos/css/themes/dark.css +0 -0
- {eidosui-0.2.0 → eidosui-0.4.0}/eidos/css/themes/eidos-variables.css +0 -0
- {eidosui-0.2.0 → eidosui-0.4.0}/eidos/css/themes/light.css +0 -0
- {eidosui-0.2.0 → eidosui-0.4.0}/eidos/styles.py +0 -0
@@ -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.4.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 🎨
|
File without changes
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from air import Meta, Script, Link
|
2
|
+
from ..tags import Body
|
3
|
+
from typing import Literal
|
4
|
+
|
5
|
+
def get_css_urls():
|
6
|
+
"""Return list of CSS URLs for EidosUI."""
|
7
|
+
return [
|
8
|
+
"/eidos/css/styles.css",
|
9
|
+
"/eidos/css/themes/eidos-variables.css",
|
10
|
+
"/eidos/css/themes/light.css",
|
11
|
+
"/eidos/css/themes/dark.css"
|
12
|
+
]
|
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
|
+
"""
|
28
|
+
headers = [
|
29
|
+
Meta(charset="UTF-8"),
|
30
|
+
Meta(name="viewport", content="width=device-width, initial-scale=1.0"),
|
31
|
+
]
|
32
|
+
|
33
|
+
# Core libraries
|
34
|
+
if include_tailwind:
|
35
|
+
headers.append(Script(src="https://cdn.tailwindcss.com"))
|
36
|
+
|
37
|
+
if include_lucide:
|
38
|
+
headers.append(Script(src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"))
|
39
|
+
|
40
|
+
# EidosUI CSS
|
41
|
+
for css_url in get_css_urls():
|
42
|
+
headers.append(Link(rel="stylesheet", href=css_url))
|
43
|
+
|
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))
|
67
|
+
|
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
|
+
)
|
@@ -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
|
+
}
|
@@ -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
|
+
)
|