Coverage for src/lexigram/admin/navigation/types.py: 98%

60 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 14:56 +0800

1"""Navigation type definitions for admin sidebar construction.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass, field 

6 

7 

8@dataclass 

9class NavItem: 

10 """Navigation item.""" 

11 

12 name: str 

13 label: str 

14 url: str 

15 icon: str | None = None 

16 badge: str | None = None 

17 badge_variant: str = "primary" 

18 active: bool = False 

19 external: bool = False 

20 

21 children: list[NavItem] | None = None 

22 

23 resource_name: str | None = None 

24 permission: str | None = None 

25 

26 

27@dataclass 

28class NavGroup: 

29 """Navigation group containing multiple items.""" 

30 

31 name: str 

32 label: str 

33 icon: str | None = None 

34 order: int = 0 

35 collapsible: bool = True 

36 collapsed: bool = False 

37 

38 items: list[NavItem] = field(default_factory=list) 

39 

40 permission: str | None = None 

41 feature_flag: str | None = None 

42 

43 

44@dataclass 

45class SidebarNavItem: 

46 """Normalized sidebar navigation item. 

47 

48 Converts ``NavigationContribution`` and resource nav entries into one 

49 shell-ready shape consumed by ``AdminShell._prepare_navigation()``. 

50 """ 

51 

52 label: str 

53 href: str 

54 icon: str | None = None 

55 badge: str | None = None 

56 active: bool = False 

57 is_group: bool = False 

58 permission: str | None = None 

59 feature: str | None = None 

60 

61 def to_dict(self) -> dict[str, str | bool | None]: 

62 """Convert to dict for backward-compatible shell consumption.""" 

63 d: dict[str, str | bool | None] = { 

64 "label": self.label, 

65 "href": self.href, 

66 "icon": self.icon, 

67 "badge": self.badge, 

68 "active": self.active, 

69 } 

70 if self.is_group: 

71 d["is_group"] = True 

72 if self.permission: 

73 d["permission"] = self.permission 

74 if self.feature: 

75 d["feature"] = self.feature 

76 return d 

77 

78 

79@dataclass(frozen=True) 

80class MenuItem: 

81 """User-menu entry rendered in the shell's user dropdown. 

82 

83 ``action`` is an optional semantic marker (e.g. ``"logout"``); plain 

84 entries are plain links to ``href``. 

85 """ 

86 

87 label: str 

88 href: str 

89 icon: str | None = None 

90 action: str | None = None 

91 

92 def to_dict(self) -> dict[str, str | None]: 

93 """Convert to a shell-compatible dict.""" 

94 return { 

95 "label": self.label, 

96 "href": self.href, 

97 "icon": self.icon, 

98 "action": self.action, 

99 } 

100 

101 

102@dataclass 

103class NavigationConfig: 

104 """Configuration for navigation assembly.""" 

105 

106 prefix: str = "/admin" 

107 

108 groups: dict[str, NavGroup] = field(default_factory=dict) 

109 

110 default_icons: dict[str, str] = field( 

111 default_factory=lambda: { 

112 "users": "users", 

113 "products": "cube", 

114 "orders": "shopping-cart", 

115 "settings": "cog", 

116 "reports": "chart-bar", 

117 "logs": "document-text", 

118 }, 

119 ) 

120 

121 show_counts: bool = False 

122 

123 

124__all__ = ["NavGroup", "NavItem", "NavigationConfig", "SidebarNavItem"]