Coverage for src / lexigram / ui / molecules / card.py: 100%

41 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-10 04:11 +0800

1from __future__ import annotations 

2 

3from typing import Any 

4 

5from lexigram.ui.atoms.button import Button 

6from lexigram.ui.core.base import Component, el, raw, render_to_string 

7 

8 

9class Card(Component): 

10 """Refined Card component for better aesthetics.""" 

11 

12 def __init__( 

13 self, 

14 title: str | Any | None = None, 

15 content: str | Any | None = None, 

16 footer: Any = None, 

17 *, 

18 as_child: bool = False, 

19 **props, 

20 ) -> None: 

21 super().__init__( 

22 title=title, content=content, footer=footer, as_child=as_child, **props 

23 ) 

24 self.title = title 

25 self.content = content 

26 self.footer = footer 

27 

28 def render(self) -> Any: 

29 # Merge classes 

30 cls = "bg-card text-card-foreground shadow rounded-xl overflow-hidden mb-6 border border-border transition-colors duration-300" 

31 custom_cls = self.props.get("class_", self.props.get("class")) 

32 if custom_cls: 

33 cls = f"{cls} {custom_cls}" 

34 

35 # Clean attrs 

36 attrs = { 

37 k: v 

38 for k, v in self.props.items() 

39 if k not in ("title", "content", "footer", "actions", "class_", "class") 

40 } 

41 

42 # Header 

43 header = None 

44 if self.title: 

45 title_content = self.title 

46 if not hasattr(title_content, "__html__"): 

47 title_content = str(title_content) 

48 header = el( 

49 "div", 

50 title_content, 

51 class_="px-6 py-4 border-b border-border font-semibold text-card-foreground card-header", 

52 ) 

53 

54 # Body 

55 body_content = self.content or "" 

56 if not hasattr(body_content, "__html__"): 

57 body_content = render_to_string(body_content) 

58 

59 # Add any children from Streamlit-style usage 

60 if self.children: 

61 body_content += "".join(render_to_string(c) for c in self.children) 

62 

63 body = el("div", raw(body_content), class_="px-6 py-4 card-body") 

64 

65 # Footer / Actions (for backward compatibility) 

66 footer_el = None 

67 actions = self.props.get("actions", []) 

68 

69 if self.footer or actions: 

70 footer_children = [] 

71 if self.footer: 

72 footer_children.append(self.footer) 

73 

74 for a in actions: 

75 if hasattr(a, "__html__") or ( 

76 hasattr(a, "render") and callable(a.render) 

77 ): 

78 footer_children.append(a) 

79 else: 

80 # Render string actions as buttons 

81 footer_children.append( 

82 Button(str(a), color="primary", class_="mx-1"), 

83 ) 

84 

85 footer_content = raw("".join(render_to_string(c) for c in footer_children)) 

86 footer_el = el( 

87 "div", 

88 footer_content, 

89 class_="px-6 py-4 bg-muted border-t border-border card-footer", 

90 ) 

91 

92 return el("div", header, body, footer_el, class_=cls, **attrs)