Skip to content

Commit 299d22c

Browse files
authored
feat: advanced components (#886)
1 parent 3f463d2 commit 299d22c

File tree

8 files changed

+144
-13
lines changed

8 files changed

+144
-13
lines changed

src/unfold/components.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from typing import Any, Dict, Optional, Type
2+
3+
from django.http import HttpRequest
4+
5+
6+
class ComponentRegistry:
7+
_registry: Dict[str, Type] = {}
8+
9+
@classmethod
10+
def register_class(cls, component_cls: Type) -> None:
11+
if not issubclass(component_cls, BaseComponent):
12+
raise ValueError(
13+
f"Class '{component_cls.__name__}' must inherit from BaseComponent."
14+
)
15+
16+
class_name = component_cls.__name__
17+
18+
if class_name in cls._registry:
19+
raise ValueError(f"Class '{class_name}' is already registered.")
20+
21+
cls._registry[class_name] = component_cls
22+
23+
@classmethod
24+
def get_class(cls, class_name: str) -> Optional[Type]:
25+
return cls._registry.get(class_name)
26+
27+
@classmethod
28+
def create_instance(cls, class_name: str, **kwargs: Any) -> Any:
29+
component_cls = cls.get_class(class_name)
30+
31+
if component_cls is None:
32+
raise ValueError(f"Class '{class_name}' is not registered.")
33+
34+
return component_cls(**kwargs)
35+
36+
37+
def register_component(cls: Type) -> Type:
38+
ComponentRegistry.register_class(cls)
39+
return cls
40+
41+
42+
class BaseComponent:
43+
def __init__(self, request: HttpRequest):
44+
self.request = request
45+
46+
def get_context_data(self, **kwargs):
47+
return kwargs

src/unfold/static/unfold/css/styles.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/unfold/templates/unfold/components/card.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="border flex flex-col flex-grow overflow-hidden p-6 relative rounded-md shadow-sm dark:border-gray-800 {% if class %} {{ class }}{% endif %}">
22
{% if title %}
3-
<h2 class="border-b font-semibold mb-6 -mt-2 -mx-6 pb-4 px-6 text-font-important-light dark:text-font-important-dark dark:border-gray-800">
3+
<h2 class="bg-gray-50 border-b font-semibold mb-6 -mt-6 -mx-6 py-4 px-6 text-font-important-light dark:text-font-important-dark dark:border-gray-800 dark:bg-white/[.02]">
44
{{ title }}
55
</h2>
66
{% endif %}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<div class="overflow-auto w-full" data-simplebar>
2+
<table class="w-full">
3+
<thead>
4+
<tr>
5+
<th></th>
6+
7+
{% for header in data.headers %}
8+
<th class="font-normal px-3 pb-2.5 text-left ">
9+
<div class="font-semibold text-font-important-light truncate dark:text-font-important-dark">
10+
{{ header.title }}
11+
</div>
12+
13+
{% if header.subtitle %}
14+
<div class="mt-0.5 text-xs truncate">
15+
{{ header.subtitle }}
16+
</div>
17+
{% endif %}
18+
</th>
19+
{% endfor %}
20+
</tr>
21+
</thead>
22+
23+
<tbody>
24+
{% for row in data.rows %}
25+
<tr class="h-full">
26+
<td>
27+
<div class="pr-3 py-2.5">
28+
<div class="font-semibold text-font-important-light dark:text-font-important-dark">
29+
{{ row.header.title }}
30+
</div>
31+
32+
{% if row.header.subtitle %}
33+
<div class="mt-0.5 text-xs">
34+
{{ row.header.subtitle }}
35+
</div>
36+
{% endif %}
37+
</div>
38+
</td>
39+
40+
{% for col in row.cols %}
41+
<td class="h-full">
42+
<div class="flex flex-col h-full justify-center px-3 py-2.5 rounded-md {% if col.color %}{{ col.color }}{% else %}bg-gray-50 border border-dashed dark:bg-gray-800 dark:border-gray-700{% endif %}">
43+
{{ col.value }}
44+
</div>
45+
</td>
46+
{% endfor %}
47+
</tr>
48+
{% endfor %}
49+
</tbody>
50+
</table>
51+
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<ul class="flex flex-row gap-0.5 overflow-hidden rounded">
2+
{% for item in data %}
3+
<li class="h-8 px-px size-full {% if item.color %}{{ item.color }}{% else %}bg-gray-300 dark:bg-gray-400{% endif %} hover:opacity-50" title="{{ item.tooltip }}"></li>
4+
{% endfor %}
5+
</ul>

src/unfold/templates/unfold/layouts/skeleton.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858

5959
{% if colors %}
6060
<style>
61-
html {
61+
:root {
6262
{% for name, weights in colors.items %}
6363
{% for weight, value in weights.items %}
6464
--color-{{ name }}-{{ weight }}: {{ value }};

src/unfold/templatetags/unfold.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from django.template.loader import render_to_string
1111
from django.utils.safestring import SafeText
1212

13+
from unfold.components import ComponentRegistry
14+
1315
register = Library()
1416

1517

@@ -154,24 +156,37 @@ def __init__(
154156
template_name: str,
155157
nodelist: NodeList,
156158
extra_context: Optional[Dict] = None,
159+
include_context: bool = False,
157160
*args,
158161
**kwargs,
159162
):
160163
self.template_name = template_name
161164
self.nodelist = nodelist
162165
self.extra_context = extra_context or {}
166+
self.include_context = include_context
163167
super().__init__(*args, **kwargs)
164168

165169
def render(self, context: RequestContext) -> str:
166-
result = self.nodelist.render(context)
170+
values = {
171+
name: var.resolve(context) for name, var in self.extra_context.items()
172+
}
173+
174+
values.update(
175+
{
176+
"children": self.nodelist.render(context),
177+
}
178+
)
167179

168-
ctx = {name: var.resolve(context) for name, var in self.extra_context.items()}
169-
ctx.update({"children": result})
180+
if "component_class" in values:
181+
values = ComponentRegistry.create_instance(
182+
values["component_class"], request=context.request
183+
).get_context_data(**values)
184+
185+
if self.include_context:
186+
values.update(context.flatten())
170187

171188
return render_to_string(
172-
self.template_name,
173-
request=context.request,
174-
context=ctx,
189+
self.template_name, request=context.request, context=values
175190
)
176191

177192

@@ -202,17 +217,21 @@ def do_component(parser: Parser, token: Token) -> str:
202217
raise TemplateSyntaxError(
203218
'"with" in {bits[0]} tag needs at least one keyword argument.'
204219
)
220+
elif option == "include_context":
221+
value = True
205222
else:
206223
raise TemplateSyntaxError(f"Unknown argument for {bits[0]} tag: {option}.")
207224

208225
options[option] = value
209226

227+
include_context = options.get("include_context", False)
210228
nodelist = parser.parse(("endcomponent",))
211229
template_name = bits[1][1:-1]
230+
212231
extra_context = options.get("with", {})
213232
parser.next_token()
214233

215-
return RenderComponentNode(template_name, nodelist, extra_context)
234+
return RenderComponentNode(template_name, nodelist, extra_context, include_context)
216235

217236

218237
@register.filter

tailwind.config.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,24 @@ module.exports = {
7575
"md:border-r",
7676
"md:w-48",
7777
{
78-
pattern: /bg-primary-+/
78+
pattern: /col-span-+/,
79+
variants: ["md", "lg"],
80+
},
81+
{
82+
pattern: /grid-cols-+/,
83+
variants: ["md", "lg"],
7984
},
8085
{
8186
pattern: /gap-+/,
82-
variants: ["lg"],
87+
variants: ["md", "lg"],
88+
},
89+
{
90+
pattern: /bg-(primary)-(50|100|200|300|400|500|600|700|800|900|950)/,
91+
variants: ["dark"],
8392
},
8493
{
8594
pattern: /w-(1\/2|1\/3|2\/3|1\/4|2\/4|3\/4|1\/5|2\/5|3\/5|4\/5)/,
86-
variants: ["lg"],
95+
variants: ["md", "lg"],
8796
},
8897
],
8998
};

0 commit comments

Comments
 (0)