Coverage for fastblocks/adapters/styles/bulma.py: 86%
42 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-09 00:47 -0700
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-09 00:47 -0700
1"""Bulma CSS framework adapter implementation."""
3from contextlib import suppress
4from typing import Any
5from uuid import UUID
7from acb.config import Settings
8from acb.depends import depends
10from ._base import StylesBase
13class BulmaSettings(Settings): # type: ignore[misc]
14 """Bulma-specific settings."""
16 version: str = "0.9.4"
17 cdn_url: str = "https://cdn.jsdelivr.net/npm/bulma@{version}/css/bulma.min.css"
18 custom_variables: dict[str, str] = {}
21class BulmaAdapter(StylesBase):
22 """Bulma CSS framework adapter implementation."""
24 # Required ACB 0.19.0+ metadata
25 MODULE_ID: UUID = UUID("01937d86-4f2a-7b3c-8d9e-f3b4d3c2c1a1") # Static UUID7
26 MODULE_STATUS = "stable"
28 # Component class mappings for Bulma
29 COMPONENT_CLASSES = {
30 "button": "button",
31 "button_primary": "button is-primary",
32 "button_secondary": "button is-light",
33 "button_success": "button is-success",
34 "button_danger": "button is-danger",
35 "button_warning": "button is-warning",
36 "button_info": "button is-info",
37 "button_small": "button is-small",
38 "button_medium": "button is-medium",
39 "button_large": "button is-large",
40 "input": "input",
41 "textarea": "textarea",
42 "select": "select",
43 "checkbox": "checkbox",
44 "radio": "radio",
45 "field": "field",
46 "label": "label",
47 "control": "control",
48 "card": "card",
49 "card_header": "card-header",
50 "card_content": "card-content",
51 "card_footer": "card-footer",
52 "hero": "hero",
53 "hero_body": "hero-body",
54 "section": "section",
55 "container": "container",
56 "columns": "columns",
57 "column": "column",
58 "navbar": "navbar",
59 "navbar_brand": "navbar-brand",
60 "navbar_menu": "navbar-menu",
61 "navbar_item": "navbar-item",
62 "footer": "footer",
63 "modal": "modal",
64 "modal_background": "modal-background",
65 "modal_content": "modal-content",
66 "modal_close": "modal-close",
67 "notification": "notification",
68 "tag": "tag",
69 "title": "title",
70 "subtitle": "subtitle",
71 }
73 def __init__(self) -> None:
74 """Initialize Bulma adapter."""
75 super().__init__()
76 self.settings = BulmaSettings()
78 # Register with ACB dependency system
79 with suppress(Exception):
80 depends.set(self)
82 def get_stylesheet_links(self) -> list[str]:
83 """Generate Bulma stylesheet link tags."""
84 cdn_url = self.settings.cdn_url.format(version=self.settings.version)
85 return [f'<link rel="stylesheet" href="{cdn_url}">']
87 def get_component_class(self, component: str) -> str:
88 """Get Bulma-specific class names for components."""
89 return self.COMPONENT_CLASSES.get(component, component)
91 def get_utility_classes(self) -> dict[str, str]:
92 """Get common Bulma utility classes."""
93 return {
94 "text_center": "has-text-centered",
95 "text_left": "has-text-left",
96 "text_right": "has-text-right",
97 "text_weight_bold": "has-text-weight-bold",
98 "text_weight_light": "has-text-weight-light",
99 "background_primary": "has-background-primary",
100 "background_secondary": "has-background-light",
101 "text_primary": "has-text-primary",
102 "text_secondary": "has-text-dark",
103 "margin_small": "m-2",
104 "margin_medium": "m-4",
105 "margin_large": "m-6",
106 "padding_small": "p-2",
107 "padding_medium": "p-4",
108 "padding_large": "p-6",
109 "is_hidden": "is-hidden",
110 "is_visible": "is-visible",
111 "is_responsive": "is-responsive",
112 }
114 def build_component_html(
115 self, component: str, content: str = "", **attributes: Any
116 ) -> str:
117 """Build complete HTML component with Bulma classes."""
118 css_class = self.get_component_class(component)
120 # Add any additional classes
121 if "class" in attributes:
122 css_class = f"{css_class} {attributes.pop('class')}"
124 # Build attributes string
125 attr_parts = [f'class="{css_class}"']
126 for key, value in attributes.items():
127 if key not in ("transformations"): # Skip internal attributes
128 attr_parts.append(f'{key}="{value}"')
130 attrs_str = " ".join(attr_parts)
132 # Determine the appropriate HTML tag based on component type
133 if component.startswith("button"):
134 return f"<button {attrs_str}>{content}</button>"
135 elif component in ("input", "textarea", "select"):
136 return f"<{component} {attrs_str}>"
137 elif component == "field":
138 return f"<div {attrs_str}>{content}</div>"
140 return f"<div {attrs_str}>{content}</div>"