Coverage for src / lexigram / ui / organisms / admin.py: 92%

48 statements  

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

1"""Admin UI organisms — AdminCard and PageLayout. 

2 

3Components for building admin dashboard pages with ShadCN-compatible styling. 

4""" 

5 

6from __future__ import annotations 

7 

8from typing import Any 

9 

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

11 

12 

13class AdminCard(Component): 

14 """ShadCN-styled card for admin dashboard sections. 

15 

16 Args: 

17 title: Optional card header text. 

18 content: Card body — a string, htpy element, or Component. 

19 **props: Additional HTML attributes applied to the outer div. 

20 """ 

21 

22 def __init__( 

23 self, 

24 title: str | Any | None = None, 

25 content: str | Any | None = None, 

26 **props: Any, 

27 ) -> None: 

28 super().__init__(title=title, content=content, **props) 

29 self.title = title 

30 self.content = content 

31 

32 def render(self) -> Any: 

33 cls = ( 

34 "bg-card text-card-foreground border border-border rounded-xl " 

35 "shadow-sm overflow-hidden" 

36 ) 

37 custom_cls = self.props.get("class_") or self.props.get("class") 

38 if custom_cls: 

39 cls = f"{cls} {custom_cls}" 

40 

41 attrs = { 

42 k: v 

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

44 if k not in ("title", "content", "class_", "class") 

45 } 

46 

47 header_el = None 

48 if self.title: 

49 header_el = el( 

50 "div", 

51 str(self.title), 

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

53 ) 

54 

55 body_content: str 

56 if self.content is None: 

57 body_content = "" 

58 elif hasattr(self.content, "__html__") or hasattr(self.content, "render"): 

59 body_content = render_to_string(self.content) 

60 else: 

61 body_content = str(self.content) 

62 

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

64 

65 return el("div", header_el, body_el, class_=cls, **attrs) 

66 

67 

68class PageLayout(Component): 

69 """Full-page admin layout wrapper with a header bar and content area. 

70 

71 Args: 

72 title: Page title rendered in the header. 

73 children: Main page content — string, htpy element, Component, or list thereof. 

74 actions: Optional list of elements (e.g. Buttons) rendered in the header right side. 

75 **props: Additional HTML attributes applied to the outer wrapper div. 

76 """ 

77 

78 def __init__( 

79 self, 

80 title: str = "", 

81 children: Any = None, 

82 actions: list[Any] | None = None, 

83 **props: Any, 

84 ) -> None: 

85 super().__init__(**props) 

86 self.title = title 

87 if children is not None: 

88 self.children = children if isinstance(children, list) else [children] 

89 else: 

90 self.children = [] 

91 self.actions = actions or [] 

92 

93 def render(self) -> Any: 

94 wrapper_cls = "flex flex-col min-h-full" 

95 custom_cls = self.props.get("class_") or self.props.get("class") 

96 if custom_cls: 

97 wrapper_cls = f"{wrapper_cls} {custom_cls}" 

98 

99 attrs = { 

100 k: v 

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

102 if k not in ("title", "children", "actions", "class_", "class") 

103 } 

104 

105 title_el = el( 

106 "h1", 

107 self.title, 

108 class_="text-2xl font-semibold text-foreground", 

109 ) 

110 

111 actions_el = None 

112 if self.actions: 

113 rendered_actions = [ 

114 raw(render_to_string(a)) 

115 if hasattr(a, "__html__") or hasattr(a, "render") 

116 else str(a) 

117 for a in self.actions 

118 ] 

119 actions_el = el( 

120 "div", 

121 *rendered_actions, 

122 class_="flex items-center gap-2", 

123 ) 

124 

125 header_el = el( 

126 "div", 

127 title_el, 

128 actions_el, 

129 class_="flex items-center justify-between px-6 py-4 border-b border-border bg-background", 

130 ) 

131 

132 rendered_children = [ 

133 raw(render_to_string(c)) 

134 if hasattr(c, "__html__") or hasattr(c, "render") 

135 else str(c) 

136 for c in self.children 

137 ] 

138 content_el = el( 

139 "div", 

140 *rendered_children, 

141 class_="flex-1 p-6", 

142 ) 

143 

144 return el("div", header_el, content_el, class_=wrapper_cls, **attrs) 

145 

146 

147__all__ = ["AdminCard", "PageLayout"]