eidosui 0.4.0__py3-none-any.whl → 0.6.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/css/styles.css CHANGED
@@ -339,6 +339,54 @@
339
339
  margin: var(--space-xl) 0;
340
340
  }
341
341
 
342
+ /* Table styles */
343
+ .eidos-table {
344
+ width: 100%;
345
+ border-collapse: collapse;
346
+ font-size: var(--font-size-base);
347
+ background-color: var(--color-background);
348
+ }
349
+
350
+ .eidos-thead {
351
+ background-color: var(--color-surface);
352
+ border-bottom: var(--border-2) solid var(--color-border);
353
+ }
354
+
355
+ .eidos-tbody {
356
+ background-color: var(--color-background);
357
+ }
358
+
359
+ .eidos-tfoot {
360
+ background-color: var(--color-surface);
361
+ border-top: var(--border-2) solid var(--color-border);
362
+ }
363
+
364
+ .eidos-tr {
365
+ border-bottom: var(--border) solid var(--color-border);
366
+ transition: background-color var(--transition-fast);
367
+ }
368
+
369
+ .eidos-tbody .eidos-tr:hover {
370
+ background-color: var(--color-surface);
371
+ }
372
+
373
+ .eidos-tbody .eidos-tr:last-child {
374
+ border-bottom: none;
375
+ }
376
+
377
+ .eidos-th {
378
+ padding: var(--space-sm) var(--space-md);
379
+ text-align: left;
380
+ font-weight: var(--font-weight-semibold);
381
+ color: var(--color-text);
382
+ }
383
+
384
+ .eidos-td {
385
+ padding: var(--space-sm) var(--space-md);
386
+ text-align: left;
387
+ color: var(--color-text);
388
+ }
389
+
342
390
  /* Navigation base styles */
343
391
  .eidos-navbar {
344
392
  width: 100%;
@@ -427,4 +475,111 @@
427
475
  transform: translateY(0);
428
476
  opacity: 1;
429
477
  }
478
+ }
479
+
480
+ /* List Styles */
481
+ .eidos-ul {
482
+ list-style-type: disc;
483
+ margin: var(--space-md) 0;
484
+ padding-left: var(--space-xl);
485
+ color: var(--color-text);
486
+ }
487
+
488
+ .eidos-ol {
489
+ list-style-type: decimal;
490
+ margin: var(--space-md) 0;
491
+ padding-left: var(--space-xl);
492
+ color: var(--color-text);
493
+ }
494
+
495
+ .eidos-li {
496
+ margin-bottom: var(--space-xs);
497
+ line-height: var(--line-height-relaxed);
498
+ }
499
+
500
+ /* Nested lists */
501
+ .eidos-ul .eidos-ul,
502
+ .eidos-ul .eidos-ol,
503
+ .eidos-ol .eidos-ul,
504
+ .eidos-ol .eidos-ol {
505
+ margin: var(--space-xs) 0;
506
+ }
507
+
508
+ /* Remove bottom margin from last list item */
509
+ .eidos-li:last-child {
510
+ margin-bottom: 0;
511
+ }
512
+
513
+ /* List item hover effect for interactive lists */
514
+ .eidos-li-interactive {
515
+ padding: var(--space-xs) var(--space-sm);
516
+ margin-left: calc(var(--space-xl) * -1);
517
+ margin-right: calc(var(--space-sm) * -1);
518
+ padding-left: var(--space-xl);
519
+ border-radius: var(--radius-sm);
520
+ transition: background-color var(--transition-fast);
521
+ cursor: pointer;
522
+ }
523
+
524
+ .eidos-li-interactive:hover {
525
+ background-color: var(--color-surface);
526
+ }
527
+
528
+ /* Tabs Component */
529
+ .eidos-tabs {
530
+ position: relative;
531
+ }
532
+
533
+ .eidos-tabs-list {
534
+ display: flex;
535
+ border-bottom: 1px solid var(--color-border);
536
+ overflow-x: auto;
537
+ scrollbar-width: thin;
538
+ -webkit-overflow-scrolling: touch;
539
+ }
540
+
541
+ .eidos-tab {
542
+ padding: var(--space-sm) var(--space-lg);
543
+ border-bottom: 2px solid transparent;
544
+ color: var(--color-text-muted);
545
+ text-decoration: none;
546
+ white-space: nowrap;
547
+ transition: all var(--transition-fast);
548
+ position: relative;
549
+ top: 1px;
550
+ }
551
+
552
+ .eidos-tab:hover {
553
+ color: var(--color-text);
554
+ }
555
+
556
+ .eidos-tab:focus {
557
+ outline: 2px solid var(--color-focus);
558
+ outline-offset: -2px;
559
+ }
560
+
561
+ .eidos-tab-active {
562
+ color: var(--color-primary);
563
+ border-bottom-color: var(--color-primary);
564
+ }
565
+
566
+ .eidos-tab-panel {
567
+ padding: var(--space-lg) 0;
568
+ display: none;
569
+ }
570
+
571
+ .eidos-tab-panel-active {
572
+ display: block;
573
+ }
574
+
575
+ /* HTMX-enhanced transitions */
576
+ .eidos-tabs.htmx-swapping .eidos-tab-panel {
577
+ opacity: 0;
578
+ transform: translateY(-4px);
579
+ }
580
+
581
+ .eidos-tabs.htmx-settling .eidos-tab-panel {
582
+ opacity: 1;
583
+ transform: translateY(0);
584
+ transition: opacity var(--transition-normal), transform var(--transition-normal);
430
585
  }
eidos/plugins/__init__.py CHANGED
@@ -1 +1 @@
1
- # EidosUI plugins package
1
+ # EidosUI plugins package
@@ -5,10 +5,10 @@ EidosUI themes through CSS variables.
5
5
 
6
6
  Basic usage:
7
7
  from eidos.plugins.markdown import Markdown, MarkdownCSS
8
-
8
+
9
9
  # In your document head
10
10
  MarkdownCSS()
11
-
11
+
12
12
  # In your content
13
13
  Markdown("# Hello World\\n\\nThis is **markdown**!")
14
14
  """
@@ -18,4 +18,4 @@ from .renderer import MarkdownRenderer
18
18
 
19
19
  __all__ = ["Markdown", "MarkdownCSS", "MarkdownRenderer"]
20
20
 
21
- __version__ = "0.1.0"
21
+ __version__ = "0.1.0"
@@ -1,53 +1,41 @@
1
1
  """Markdown components for EidosUI"""
2
2
 
3
3
  import air
4
- from typing import Optional
5
- from .renderer import MarkdownRenderer
6
4
 
5
+ from .renderer import MarkdownRenderer
7
6
 
8
7
  # Global renderer instance for reuse
9
8
  _renderer = MarkdownRenderer()
10
9
 
11
10
 
12
- def Markdown(content: str, class_: Optional[str] = None, **kwargs) -> air.Div:
11
+ def Markdown(content: str, class_: str | None = None, **kwargs) -> air.Div:
13
12
  """Main markdown component that renders markdown content with theme integration.
14
-
13
+
15
14
  Args:
16
15
  content: Markdown text to render
17
16
  class_: Additional CSS classes to apply
18
17
  **kwargs: Additional attributes to pass to the wrapper div
19
-
18
+
20
19
  Returns:
21
20
  air.Div containing the rendered markdown HTML
22
21
  """
23
22
  # Render the markdown content
24
23
  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
- )
24
+
25
+ return air.Div(air.RawHTML(html_content), class_=class_, **kwargs)
38
26
 
39
27
 
40
28
  def MarkdownCSS() -> air.Link:
41
29
  """Returns a link tag to include the markdown CSS.
42
-
30
+
43
31
  This should be included in the head of your document to ensure
44
32
  markdown styling is available.
45
-
33
+
46
34
  Returns:
47
35
  air.Link element pointing to the markdown CSS file
48
36
  """
49
37
  return air.Link(
50
38
  rel="stylesheet",
51
39
  href="/eidos/plugins/markdown/css/markdown.css",
52
- type="text/css"
53
- )
40
+ type="text/css",
41
+ )
@@ -1 +1 @@
1
- # Markdown plugin extensions
1
+ # Markdown plugin extensions
@@ -1,134 +1,124 @@
1
1
  """GitHub-style alerts extension for markdown"""
2
2
 
3
3
  import re
4
- from markdown.extensions import Extension
5
- from markdown.blockprocessors import BlockProcessor
6
4
  import xml.etree.ElementTree as etree
7
5
  from xml.etree.ElementTree import SubElement
8
6
 
7
+ from markdown.blockprocessors import BlockProcessor
8
+ from markdown.extensions import Extension
9
+
9
10
 
10
11
  class AlertBlockProcessor(BlockProcessor):
11
12
  """Process GitHub-style alert blocks"""
12
-
13
+
13
14
  # Pattern to match > [!TYPE] at the start of a blockquote
14
- RE_ALERT = re.compile(r'^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]', re.MULTILINE)
15
-
15
+ RE_ALERT = re.compile(r"^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]", re.MULTILINE)
16
+
16
17
  # Alert type configurations
17
18
  ALERT_TYPES = {
18
- 'NOTE': {
19
- 'class': 'eidos-alert eidos-alert-info',
20
- 'icon': 'ℹ️',
21
- 'title': 'Note'
19
+ "NOTE": {"class": "eidos-alert eidos-alert-info", "icon": "ℹ️", "title": "Note"},
20
+ "TIP": {
21
+ "class": "eidos-alert eidos-alert-success",
22
+ "icon": "💡",
23
+ "title": "Tip",
22
24
  },
23
- 'TIP': {
24
- 'class': 'eidos-alert eidos-alert-success',
25
- 'icon': '💡',
26
- 'title': 'Tip'
25
+ "IMPORTANT": {
26
+ "class": "eidos-alert eidos-alert-warning",
27
+ "icon": "❗",
28
+ "title": "Important",
27
29
  },
28
- 'IMPORTANT': {
29
- 'class': 'eidos-alert eidos-alert-warning',
30
- 'icon': '❗',
31
- 'title': 'Important'
30
+ "WARNING": {
31
+ "class": "eidos-alert eidos-alert-warning",
32
+ "icon": "⚠️",
33
+ "title": "Warning",
32
34
  },
33
- 'WARNING': {
34
- 'class': 'eidos-alert eidos-alert-warning',
35
- 'icon': '⚠️',
36
- 'title': 'Warning'
35
+ "CAUTION": {
36
+ "class": "eidos-alert eidos-alert-error",
37
+ "icon": "🔴",
38
+ "title": "Caution",
37
39
  },
38
- 'CAUTION': {
39
- 'class': 'eidos-alert eidos-alert-error',
40
- 'icon': '🔴',
41
- 'title': 'Caution'
42
- }
43
40
  }
44
-
41
+
45
42
  def test(self, parent, block):
46
43
  """Test if the block is a GitHub-style alert"""
47
44
  return bool(self.RE_ALERT.match(block))
48
-
45
+
49
46
  def run(self, parent, blocks):
50
47
  """Process the alert block"""
51
48
  block = blocks.pop(0)
52
-
49
+
53
50
  # Extract alert type
54
51
  match = self.RE_ALERT.match(block)
55
52
  if not match:
56
53
  return False
57
-
54
+
58
55
  alert_type = match.group(1)
59
- alert_config = self.ALERT_TYPES.get(alert_type, self.ALERT_TYPES['NOTE'])
60
-
56
+ alert_config = self.ALERT_TYPES.get(alert_type, self.ALERT_TYPES["NOTE"])
57
+
61
58
  # Create the alert container
62
- alert_div = SubElement(parent, 'div')
63
- alert_div.set('class', alert_config['class'])
64
-
59
+ alert_div = SubElement(parent, "div")
60
+ alert_div.set("class", alert_config["class"])
61
+
65
62
  # 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
-
63
+ header = SubElement(alert_div, "div")
64
+ header.set("class", "eidos-alert-header")
65
+
66
+ icon_span = SubElement(header, "span")
67
+ icon_span.set("class", "eidos-alert-icon")
68
+ icon_span.text = alert_config["icon"]
69
+
70
+ title_span = SubElement(header, "span")
71
+ title_span.set("class", "eidos-alert-title")
72
+ title_span.text = alert_config["title"]
73
+
74
+ content_div = SubElement(alert_div, "div")
75
+ content_div.set("class", "eidos-alert-content")
76
+
81
77
  # 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')
78
+ content = self.RE_ALERT.sub("", block)
79
+
80
+ lines = content.split("\n")
86
81
  processed_lines = []
87
-
82
+
88
83
  for line in lines:
89
84
  # Remove leading '>' from each line
90
- if line.startswith('>'):
85
+ if line.startswith(">"):
91
86
  line = line[1:].lstrip()
92
87
  processed_lines.append(line)
93
-
94
- # Join the content and parse it
95
- content_text = '\n'.join(processed_lines).strip()
96
-
88
+
89
+ content_text = "\n".join(processed_lines).strip()
90
+
97
91
  # Parse the content as markdown
98
92
  if content_text:
99
- # Create a temporary element to hold parsed content
100
- temp_element = etree.Element('div')
93
+ temp_element = etree.Element("div")
101
94
  self.parser.parseBlocks(temp_element, [content_text])
102
-
103
- # Move all children to our content div
95
+
104
96
  for child in temp_element:
105
97
  content_div.append(child)
106
-
107
- # If no children were added, add the text directly
98
+
108
99
  if len(content_div) == 0:
109
- p = SubElement(content_div, 'p')
100
+ p = SubElement(content_div, "p")
110
101
  p.text = content_text
111
-
102
+
112
103
  # Continue processing subsequent blocks that might be part of the alert
113
- while blocks and blocks[0].startswith('>'):
104
+ while blocks and blocks[0].startswith(">"):
114
105
  continuation = blocks.pop(0)
115
- # Remove leading '>' and process
116
- continuation_text = continuation[1:].lstrip() if continuation.startswith('>') else continuation
117
-
106
+ continuation_text = continuation[1:].lstrip() if continuation.startswith(">") else continuation
107
+
118
108
  if continuation_text:
119
- p = SubElement(content_div, 'p')
109
+ p = SubElement(content_div, "p")
120
110
  p.text = continuation_text
121
-
111
+
122
112
  return True
123
113
 
124
114
 
125
115
  class AlertExtension(Extension):
126
116
  """Add GitHub-style alerts to markdown"""
127
-
117
+
128
118
  def extendMarkdown(self, md):
129
119
  """Add the alert processor to the markdown instance"""
130
120
  md.parser.blockprocessors.register(
131
121
  AlertBlockProcessor(md.parser),
132
- 'github_alerts',
133
- 175 # Priority - process before blockquote
134
- )
122
+ "github_alerts",
123
+ 175, # Priority - process before blockquote
124
+ )
@@ -1,58 +1,53 @@
1
1
  """Core markdown rendering with theme integration"""
2
2
 
3
3
  import markdown
4
- from typing import Optional, List, Union
4
+
5
5
  from .extensions.alerts import AlertExtension
6
6
 
7
7
 
8
8
  class MarkdownRenderer:
9
9
  """Core markdown rendering with theme integration"""
10
-
11
- def __init__(self, extensions: Optional[List[Union[str, markdown.Extension]]] = None):
10
+
11
+ def __init__(self, extensions: list[str | markdown.Extension] | None = None):
12
12
  """Initialize the renderer with optional extensions.
13
-
13
+
14
14
  Args:
15
15
  extensions: List of markdown extension names or instances to enable
16
16
  """
17
17
  self.extensions = extensions or []
18
18
  # Add some useful default extensions
19
19
  default_extensions = [
20
- 'fenced_code',
21
- 'tables',
22
- 'nl2br',
23
- 'sane_lists',
24
- AlertExtension() # Add GitHub-style alerts
20
+ "fenced_code",
21
+ "tables",
22
+ "nl2br",
23
+ "sane_lists",
24
+ AlertExtension(), # GitHub-style alerts
25
25
  ]
26
26
  self.extensions.extend(default_extensions)
27
-
28
- # Initialize the markdown processor
27
+
29
28
  self.md = markdown.Markdown(extensions=self.extensions)
30
-
29
+
31
30
  def render(self, markdown_text: str) -> str:
32
31
  """Convert markdown to themed HTML.
33
-
32
+
34
33
  Args:
35
34
  markdown_text: Raw markdown text to render
36
-
35
+
37
36
  Returns:
38
37
  HTML string wrapped with eidos-md class for styling
39
38
  """
40
- # Reset the markdown processor to clear any state
41
- self.md.reset()
42
-
43
- # Convert markdown to HTML
39
+ self.md.reset() # TODO: this is a hack to clear the state of the markdown processor
40
+
44
41
  html_content = self.md.convert(markdown_text)
45
-
46
- # Wrap in a div with our markdown class for styling
42
+
47
43
  return f'<div class="eidos-md">{html_content}</div>'
48
-
44
+
49
45
  def add_extension(self, extension: str) -> None:
50
46
  """Add a markdown extension.
51
-
47
+
52
48
  Args:
53
49
  extension: Name of the markdown extension to add
54
50
  """
55
51
  if extension not in self.extensions:
56
52
  self.extensions.append(extension)
57
- # Recreate the markdown processor with new extensions
58
- self.md = markdown.Markdown(extensions=self.extensions)
53
+ self.md = markdown.Markdown(extensions=self.extensions)
eidos/styles.py CHANGED
@@ -6,16 +6,19 @@ Only includes classes that are actually defined in the CSS file.
6
6
 
7
7
  from typing import Final
8
8
 
9
+
9
10
  class Theme:
10
11
  """Theme-related CSS classes from styles.css."""
12
+
11
13
  body: Final[str] = "eidos-body"
12
14
 
15
+
13
16
  class Buttons:
14
17
  """Button-related CSS classes from styles.css."""
15
-
18
+
16
19
  # Base button class (required for all buttons)
17
20
  base: Final[str] = "eidos-btn"
18
-
21
+
19
22
  # Button variants
20
23
  primary: Final[str] = "eidos-btn-primary"
21
24
  secondary: Final[str] = "eidos-btn-secondary"
@@ -25,6 +28,7 @@ class Buttons:
25
28
  error: Final[str] = "eidos-btn-error"
26
29
  cta: Final[str] = "eidos-btn-cta"
27
30
 
31
+
28
32
  class Typography:
29
33
  """Typography-related CSS classes from styles.css."""
30
34
 
@@ -35,9 +39,7 @@ class Typography:
35
39
  h5: Final[str] = "eidos-h5"
36
40
  h6: Final[str] = "eidos-h6"
37
41
 
38
- class Semantic:
39
- """Semantic HTML element CSS classes from styles.css."""
40
-
42
+
41
43
  # Text formatting
42
44
  strong: Final[str] = "eidos-strong"
43
45
  i: Final[str] = "eidos-i"
@@ -47,34 +49,79 @@ class Semantic:
47
49
  var: Final[str] = "eidos-var"
48
50
  mark: Final[str] = "eidos-mark"
49
51
  time: Final[str] = "eidos-time"
50
-
52
+
51
53
  # Code elements
52
54
  code: Final[str] = "eidos-code"
53
55
  pre: Final[str] = "eidos-pre"
54
56
  kbd: Final[str] = "eidos-kbd"
55
57
  samp: Final[str] = "eidos-samp"
56
-
58
+
57
59
  # Structural elements
58
60
  blockquote: Final[str] = "eidos-blockquote"
59
61
  cite: Final[str] = "eidos-cite"
60
62
  address: Final[str] = "eidos-address"
61
63
  hr: Final[str] = "eidos-hr"
62
-
64
+
63
65
  # Interactive elements
64
66
  details: Final[str] = "eidos-details"
65
67
  summary: Final[str] = "eidos-summary"
66
68
  details_content: Final[str] = "eidos-details-content"
67
-
69
+
68
70
  # Definition list
69
71
  dl: Final[str] = "eidos-dl"
70
72
  dt: Final[str] = "eidos-dt"
71
73
  dd: Final[str] = "eidos-dd"
72
-
74
+
73
75
  # Figure
74
76
  figure: Final[str] = "eidos-figure"
75
77
  figcaption: Final[str] = "eidos-figcaption"
76
78
 
79
+
80
+ class Lists:
81
+ """List-related CSS classes from styles.css."""
82
+
83
+ ul: Final[str] = "eidos-ul"
84
+ ol: Final[str] = "eidos-ol"
85
+ li: Final[str] = "eidos-li"
86
+ li_interactive: Final[str] = "eidos-li-interactive"
87
+
88
+
89
+ class Tables:
90
+ """Table-related CSS classes from styles.css."""
91
+
92
+ # Base table class
93
+ table: Final[str] = "eidos-table"
94
+
95
+ # Table sections
96
+ thead: Final[str] = "eidos-thead"
97
+ tbody: Final[str] = "eidos-tbody"
98
+ tfoot: Final[str] = "eidos-tfoot"
99
+
100
+ # Table elements
101
+ tr: Final[str] = "eidos-tr"
102
+ th: Final[str] = "eidos-th"
103
+ td: Final[str] = "eidos-td"
104
+
105
+
106
+ class Tabs:
107
+ """Tab-related CSS classes from styles.css."""
108
+
109
+ # Container and structure
110
+ container: Final[str] = "eidos-tabs"
111
+ list: Final[str] = "eidos-tabs-list"
112
+
113
+ # Tab elements
114
+ tab: Final[str] = "eidos-tab"
115
+ tab_active: Final[str] = "eidos-tab-active"
116
+
117
+ # Panel elements
118
+ panel: Final[str] = "eidos-tab-panel"
119
+ panel_active: Final[str] = "eidos-tab-panel-active"
120
+
121
+
77
122
  # Create singleton instance for easy access
78
123
  buttons = Buttons()
79
124
  typography = Typography()
80
- semantic = Semantic()
125
+ tables = Tables()
126
+ lists = Lists()
127
+ tabs = Tabs()