authfly-jinja2 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. authfly_jinja2-0.4.0/LICENSE +21 -0
  2. authfly_jinja2-0.4.0/PKG-INFO +33 -0
  3. authfly_jinja2-0.4.0/README.md +21 -0
  4. authfly_jinja2-0.4.0/authfly/components.html.j2 +91 -0
  5. authfly_jinja2-0.4.0/authfly_jinja/__init__.py +56 -0
  6. authfly_jinja2-0.4.0/authfly_jinja/lucide_icons.py +280 -0
  7. authfly_jinja2-0.4.0/authfly_jinja/skin.py +73 -0
  8. authfly_jinja2-0.4.0/authfly_jinja2.egg-info/PKG-INFO +33 -0
  9. authfly_jinja2-0.4.0/authfly_jinja2.egg-info/SOURCES.txt +80 -0
  10. authfly_jinja2-0.4.0/authfly_jinja2.egg-info/dependency_links.txt +1 -0
  11. authfly_jinja2-0.4.0/authfly_jinja2.egg-info/requires.txt +1 -0
  12. authfly_jinja2-0.4.0/authfly_jinja2.egg-info/top_level.txt +3 -0
  13. authfly_jinja2-0.4.0/blocks/authfly-account.html.j2 +26 -0
  14. authfly_jinja2-0.4.0/blocks/authfly-consent.html.j2 +23 -0
  15. authfly_jinja2-0.4.0/blocks/authfly-notice.html.j2 +18 -0
  16. authfly_jinja2-0.4.0/blocks/authfly-request.html.j2 +26 -0
  17. authfly_jinja2-0.4.0/blocks/authfly-sign-in.html.j2 +33 -0
  18. authfly_jinja2-0.4.0/blocks/authfly-sign-up.html.j2 +31 -0
  19. authfly_jinja2-0.4.0/pyproject.toml +27 -0
  20. authfly_jinja2-0.4.0/setup.cfg +4 -0
  21. authfly_jinja2-0.4.0/tests/test_widgets.py +50 -0
  22. authfly_jinja2-0.4.0/ui/box/box.html.j2 +9 -0
  23. authfly_jinja2-0.4.0/ui/button/button.html.j2 +27 -0
  24. authfly_jinja2-0.4.0/ui/checkbox/checkbox.html.j2 +17 -0
  25. authfly_jinja2-0.4.0/ui/dialog/dialog.html.j2 +14 -0
  26. authfly_jinja2-0.4.0/ui/disclosure/disclosure.html.j2 +11 -0
  27. authfly_jinja2-0.4.0/ui/disclosure/summary.html.j2 +9 -0
  28. authfly_jinja2-0.4.0/ui/form/form.html.j2 +20 -0
  29. authfly_jinja2-0.4.0/ui/icon/icon.html.j2 +13 -0
  30. authfly_jinja2-0.4.0/ui/icon/icon.variants.json +19 -0
  31. authfly_jinja2-0.4.0/ui/image/image.html.j2 +16 -0
  32. authfly_jinja2-0.4.0/ui/image/picture.html.j2 +9 -0
  33. authfly_jinja2-0.4.0/ui/image/source.html.j2 +11 -0
  34. authfly_jinja2-0.4.0/ui/input/input.html.j2 +21 -0
  35. authfly_jinja2-0.4.0/ui/label/label.html.j2 +10 -0
  36. authfly_jinja2-0.4.0/ui/link/link.html.j2 +15 -0
  37. authfly_jinja2-0.4.0/ui/meter/meter.html.j2 +16 -0
  38. authfly_jinja2-0.4.0/ui/progress/progress.html.j2 +12 -0
  39. authfly_jinja2-0.4.0/ui/radio/radio.html.j2 +17 -0
  40. authfly_jinja2-0.4.0/ui/select/select-opt-group.html.j2 +10 -0
  41. authfly_jinja2-0.4.0/ui/select/select-option.html.j2 +11 -0
  42. authfly_jinja2-0.4.0/ui/select/select.html.j2 +20 -0
  43. authfly_jinja2-0.4.0/ui/separator/separator.html.j2 +10 -0
  44. authfly_jinja2-0.4.0/ui/switch/switch.html.j2 +19 -0
  45. authfly_jinja2-0.4.0/ui/text/text.html.j2 +9 -0
  46. authfly_jinja2-0.4.0/ui/textarea/textarea.html.j2 +17 -0
  47. authfly_jinja2-0.4.0/ui8kit_jinja/__init__.py +81 -0
  48. authfly_jinja2-0.4.0/ui8kit_jinja/classes.py +31 -0
  49. authfly_jinja2-0.4.0/ui8kit_jinja/cn_merge.py +142 -0
  50. authfly_jinja2-0.4.0/ui8kit_jinja/runtime.py +192 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Auth Fly
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: authfly-jinja2
3
+ Version: 0.4.0
4
+ Summary: Authfly widgets for Jinja2 on UI8Kit 3 primitives (CLI 3.2.1, registry 3.2.0)
5
+ License-Expression: MIT
6
+ Project-URL: Repository, https://github.com/authfly/authkit
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: Jinja2<4,>=3.1
11
+ Dynamic: license-file
12
+
13
+ # Authfly widgets — Jinja2 (UI8Kit 3)
14
+
15
+ Primitives from `npx ui8kit@3` (CLI 3.2.1, registry 3.2.0):
16
+
17
+ npx ui8kit@3 init --yes --framework jinja2 --runtime jinja2 --dir . --registry-version 3.2.0
18
+ npx ui8kit@3 add --all --runtime jinja2 --registry-version 3.2.0
19
+ pip install "Jinja2>=3.1,<4"
20
+
21
+ Installed from PyPI (`pip install authfly-jinja2`), the templates ship inside the wheel as `authfly_jinja2_templates`
22
+ and `environment()` finds them; in a checkout it uses this folder. Build: `python -m build && twine check dist/*`.
23
+
24
+ `ui/` (templates) and `ui8kit_jinja/` (runtime) are CLI-owned — never edit; `npx ui8kit@3 check`.
25
+ Authfly layer: `authfly_jinja/` (`environment()`, `skin.py`, `lucide_icons.py`), `authfly/components.html.j2`
26
+ (macros: card, stack, group, form, hidden, title, alert, badge, icon_badge, field, icon, head, messages),
27
+ `blocks/authfly-*.html.j2` (same `p` props as the React widgets).
28
+
29
+ from authfly_jinja import environment
30
+ html = environment().get_template("blocks/authfly-sign-in.html.j2").render(p=props)
31
+
32
+ Text is passed via props and autoescaped; children Markup is only built by the macros. Tests:
33
+ `python3 -m unittest discover -s tests`; parity HTML: `python3 tools/render.py ../react/tests/states.json /tmp/out`.
@@ -0,0 +1,21 @@
1
+ # Authfly widgets — Jinja2 (UI8Kit 3)
2
+
3
+ Primitives from `npx ui8kit@3` (CLI 3.2.1, registry 3.2.0):
4
+
5
+ npx ui8kit@3 init --yes --framework jinja2 --runtime jinja2 --dir . --registry-version 3.2.0
6
+ npx ui8kit@3 add --all --runtime jinja2 --registry-version 3.2.0
7
+ pip install "Jinja2>=3.1,<4"
8
+
9
+ Installed from PyPI (`pip install authfly-jinja2`), the templates ship inside the wheel as `authfly_jinja2_templates`
10
+ and `environment()` finds them; in a checkout it uses this folder. Build: `python -m build && twine check dist/*`.
11
+
12
+ `ui/` (templates) and `ui8kit_jinja/` (runtime) are CLI-owned — never edit; `npx ui8kit@3 check`.
13
+ Authfly layer: `authfly_jinja/` (`environment()`, `skin.py`, `lucide_icons.py`), `authfly/components.html.j2`
14
+ (macros: card, stack, group, form, hidden, title, alert, badge, icon_badge, field, icon, head, messages),
15
+ `blocks/authfly-*.html.j2` (same `p` props as the React widgets).
16
+
17
+ from authfly_jinja import environment
18
+ html = environment().get_template("blocks/authfly-sign-in.html.j2").render(p=props)
19
+
20
+ Text is passed via props and autoescaped; children Markup is only built by the macros. Tests:
21
+ `python3 -m unittest discover -s tests`; parity HTML: `python3 tools/render.py ../react/tests/states.json /tmp/out`.
@@ -0,0 +1,91 @@
1
+ {#- Authfly middle layer for Jinja2: macros over the UI8Kit 3 primitives (ui/*), classes from authfly_jinja.skin.
2
+ Text arguments are escaped; `children` arguments are trusted Markup built by these macros only. -#}
3
+
4
+ {%- macro prim(path, p, children='') -%}{% include path %}{%- endmacro -%}
5
+
6
+ {%- macro icon(name, size='sm') -%}
7
+ {%- set svg -%}<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%" aria-hidden="true" focusable="false">{{ af_lucide(name) }}</svg>{%- endset -%}
8
+ {{ prim('ui/icon/icon.html.j2', {'Size': size or 'sm', 'Attrs': {'data-lucide': name}}, svg) }}
9
+ {%- endmacro -%}
10
+
11
+ {%- macro box(tag='', cls='', attrs={}, children='') -%}
12
+ {{ prim('ui/box/box.html.j2', {'Tag': tag, 'Class': cls, 'Attrs': af_attrs(attrs)}, children) }}
13
+ {%- endmacro -%}
14
+
15
+ {%- macro text(tag='', cls='', attrs={}, children='') -%}
16
+ {{ prim('ui/text/text.html.j2', {'Tag': tag, 'Class': cls, 'Attrs': af_attrs(attrs)}, children) }}
17
+ {%- endmacro -%}
18
+
19
+ {%- macro card(cls='', attrs={}, children='') -%}{{ box('section', af_skin('card', cls), attrs, children) }}{%- endmacro -%}
20
+
21
+ {%- macro stack(gap='', align='', justify='', cls='', attrs={}, children='') -%}
22
+ {{ box('', af_cn('flex flex-col', af_gap(gap, '4'), af_align(align, 'stretch'), af_justify(justify), cls), attrs, children) }}
23
+ {%- endmacro -%}
24
+
25
+ {%- macro group(gap='', align='', justify='', cls='', attrs={}, children='') -%}
26
+ {{ box('', af_cn('flex min-w-0', af_gap(gap, '2'), af_align(align, 'center'), af_justify(justify), cls), attrs, children) }}
27
+ {%- endmacro -%}
28
+
29
+ {#- form: the Form primitive (method post) with the Authfly grid; the form error id is passed to each field. -#}
30
+ {%- macro form(action='', cls='', attrs={}, children='') -%}
31
+ {{ prim('ui/form/form.html.j2', {'Action': action, 'Method': 'post', 'Class': af_cn('grid gap-4', cls), 'Attrs': af_attrs(attrs)}, children) }}
32
+ {%- endmacro -%}
33
+
34
+ {#- hidden: value always emitted (an empty value goes through Attrs; the primitive drops value=""). -#}
35
+ {%- macro hidden(name, value='') -%}
36
+ {%- if value is none or value == '' -%}{{ prim('ui/input/input.html.j2', {'Type': 'hidden', 'Name': name, 'Attrs': {'value': ''}}) }}
37
+ {%- else -%}{{ prim('ui/input/input.html.j2', {'Type': 'hidden', 'Name': name, 'Value': value}) }}{%- endif -%}
38
+ {%- endmacro -%}
39
+
40
+ {%- macro title(level, id, role, title) -%}
41
+ {{ text('h1' if level == 1 else 'h2', af_text('title' if level == 1 else 'subtitle'), {'id': id, 'role': role}, af_esc(title)) }}
42
+ {%- endmacro -%}
43
+
44
+ {%- macro alert(tone, icon_name, id, testid, message) -%}
45
+ {%- set c -%}{% if icon_name %}{{ icon(icon_name) }}{% endif %}{{ message }}{%- endset -%}
46
+ {{ box('', af_skin('alert', tone), {'role': 'alert' if tone == 'destructive' else 'status', 'aria-live': 'polite', 'data-slot': 'alert', 'id': id, 'data-testid': testid}, c) }}
47
+ {%- endmacro -%}
48
+
49
+ {%- macro badge(variant, testid, label) -%}
50
+ {{ text('span', af_skin('badge', variant or 'default'), {'data-slot': 'badge', 'data-testid': testid}, af_esc(label)) }}
51
+ {%- endmacro -%}
52
+
53
+ {%- macro icon_badge(icon_name, tone='default') -%}
54
+ {{ text('span', af_skin('iconBadge', tone or 'default'), {'data-slot': 'icon-badge'}, icon(icon_name, 'lg')) }}
55
+ {%- endmacro -%}
56
+
57
+ {#- field: label + Input + hint + error. f = {label, for, hint, error, formError, input: {type, name, required, attrs},
58
+ describe: own ids, suffix: trusted Markup after the input, in a Group}. aria-describedby = own + hint + error +
59
+ form error; aria-invalid="true" when the field or the form shows an error. -#}
60
+ {%- macro field(f) -%}
61
+ {%- set hint_id = f.for ~ '-hint' if f.hint else none -%}
62
+ {%- set err_id = f.for ~ '-error' if f.error else none -%}
63
+ {%- set inp = f.input or {} -%}
64
+ {%- set attrs = dict(inp.attrs or {}) -%}{%- set _ = attrs.update({'data-testid': 'field'}) -%}
65
+ {%- set ctl -%}{{ prim('ui/input/input.html.j2', {'Type': inp.type, 'Name': inp.name, 'Required': inp.required or false, 'ID': f.for, 'Class': af_skin('input'), 'AriaDescribedBy': af_describe(f.describe, hint_id, err_id, f.formError), 'AriaInvalid': 'true' if (f.error or f.formError) else '', 'Attrs': af_attrs(attrs)}) }}{%- endset -%}
66
+ {%- if f.suffix -%}{%- set ctl = group('2', '', '', '', {}, ctl ~ f.suffix) -%}{%- endif -%}
67
+ {%- set c -%}
68
+ {{ prim('ui/label/label.html.j2', {'For': f.for, 'Class': af_skin('label')}, af_esc(f.label)) }}{{ ctl }}
69
+ {%- if hint_id %}{{ text('', af_text('hint'), {'id': hint_id}, af_esc(f.hint)) }}{% endif -%}
70
+ {%- if err_id %}{{ text('', 'text-xs font-semibold text-destructive', {'id': err_id}, af_esc(f.error)) }}{% endif -%}
71
+ {%- endset -%}
72
+ {{ box('', 'flex min-w-0 flex-col gap-2', {}, c) }}
73
+ {%- endmacro -%}
74
+
75
+ {%- macro head(id, title_, lead, align='start', role=none, extra='') -%}
76
+ {%- set c -%}{{ title(1, id ~ '-title', none, title_) }}{{ text('', af_text('muted'), {}, af_esc(lead)) }}{{ extra }}{%- endset -%}
77
+ {{ stack('2', align or 'start', '', '', {'role': role}, c) }}
78
+ {%- endmacro -%}
79
+
80
+ {%- macro messages(id, error, info) -%}
81
+ {%- if error %}{{ alert('destructive', 'circle-alert', id ~ '-error', 'authfly-error', error) }}{% endif -%}
82
+ {%- if info %}{{ alert('success', 'info', '', 'authfly-info', info) }}{% endif -%}
83
+ {%- endmacro -%}
84
+
85
+ {%- macro link(href, cls, testid, label) -%}
86
+ {{ prim('ui/link/link.html.j2', {'Href': href, 'Class': cls, 'Attrs': af_attrs({'data-testid': testid})}, af_esc(label)) }}
87
+ {%- endmacro -%}
88
+
89
+ {%- macro button(href, type, cls, attrs={}, children='') -%}
90
+ {{ prim('ui/button/button.html.j2', {'Href': href, 'Type': type, 'Class': cls, 'Attrs': af_attrs(attrs)}, children) }}
91
+ {%- endmacro -%}
@@ -0,0 +1,56 @@
1
+ """Authfly widgets for Jinja2 on the UI8Kit 3 primitives (ui/*, ui8kit_jinja — CLI-owned).
2
+
3
+ `environment()` = ui8kit_jinja.environment(root) + the af_* skin helpers. Render a widget with
4
+ `env.get_template("blocks/authfly-sign-in.html.j2").render(p=props)`; props are the xgen/react camelCase props.
5
+ All text is passed through props and autoescaped; children markup is only built by authfly/components.html.j2.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from typing import Any
12
+
13
+ from jinja2 import Environment
14
+ from jinja2.runtime import Undefined
15
+ from markupsafe import Markup, escape
16
+
17
+ import ui8kit_jinja
18
+
19
+ from . import skin
20
+ from .lucide_icons import LUCIDE
21
+
22
+ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
23
+ if not os.path.isdir(os.path.join(ROOT, "blocks")): # installed wheel: templates live in authfly_jinja2_templates
24
+ import importlib.util
25
+
26
+ _spec = importlib.util.find_spec("authfly_jinja2_templates")
27
+ if _spec and _spec.submodule_search_locations:
28
+ ROOT = list(_spec.submodule_search_locations)[0]
29
+
30
+
31
+ def _p(v: Any) -> Any:
32
+ return None if isinstance(v, Undefined) else v
33
+
34
+
35
+ def _attrs(a: Any) -> dict:
36
+ return {k: v for k, v in dict(a or {}).items() if _p(v) is not None and v != ""}
37
+
38
+
39
+ def _describe(*ids: Any) -> str:
40
+ return " ".join(str(i) for i in ids if _p(i))
41
+
42
+
43
+ def environment(root: str | None = None, **options: Any) -> Environment:
44
+ env = ui8kit_jinja.environment(root or ROOT, **options)
45
+ g = env.globals
46
+ g["af_skin"] = lambda name, *a: getattr(skin, name)(*[_p(x) for x in a])
47
+ g["af_text"] = lambda k: skin.TEXT[k]
48
+ g["af_cn"] = lambda *a: skin.cn(*[_p(x) for x in a])
49
+ g["af_gap"] = lambda k, d: skin.GAP[_p(k) or d]
50
+ g["af_align"] = lambda k, d: skin.ALIGN[_p(k) or d]
51
+ g["af_justify"] = lambda k: skin.JUSTIFY[k] if _p(k) else ""
52
+ g["af_lucide"] = lambda n: Markup(LUCIDE.get(n) or LUCIDE["circle"])
53
+ g["af_attrs"] = _attrs
54
+ g["af_describe"] = _describe
55
+ g["af_esc"] = lambda v: escape("" if _p(v) is None else v)
56
+ return env