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.
- authfly_jinja2-0.4.0/LICENSE +21 -0
- authfly_jinja2-0.4.0/PKG-INFO +33 -0
- authfly_jinja2-0.4.0/README.md +21 -0
- authfly_jinja2-0.4.0/authfly/components.html.j2 +91 -0
- authfly_jinja2-0.4.0/authfly_jinja/__init__.py +56 -0
- authfly_jinja2-0.4.0/authfly_jinja/lucide_icons.py +280 -0
- authfly_jinja2-0.4.0/authfly_jinja/skin.py +73 -0
- authfly_jinja2-0.4.0/authfly_jinja2.egg-info/PKG-INFO +33 -0
- authfly_jinja2-0.4.0/authfly_jinja2.egg-info/SOURCES.txt +80 -0
- authfly_jinja2-0.4.0/authfly_jinja2.egg-info/dependency_links.txt +1 -0
- authfly_jinja2-0.4.0/authfly_jinja2.egg-info/requires.txt +1 -0
- authfly_jinja2-0.4.0/authfly_jinja2.egg-info/top_level.txt +3 -0
- authfly_jinja2-0.4.0/blocks/authfly-account.html.j2 +26 -0
- authfly_jinja2-0.4.0/blocks/authfly-consent.html.j2 +23 -0
- authfly_jinja2-0.4.0/blocks/authfly-notice.html.j2 +18 -0
- authfly_jinja2-0.4.0/blocks/authfly-request.html.j2 +26 -0
- authfly_jinja2-0.4.0/blocks/authfly-sign-in.html.j2 +33 -0
- authfly_jinja2-0.4.0/blocks/authfly-sign-up.html.j2 +31 -0
- authfly_jinja2-0.4.0/pyproject.toml +27 -0
- authfly_jinja2-0.4.0/setup.cfg +4 -0
- authfly_jinja2-0.4.0/tests/test_widgets.py +50 -0
- authfly_jinja2-0.4.0/ui/box/box.html.j2 +9 -0
- authfly_jinja2-0.4.0/ui/button/button.html.j2 +27 -0
- authfly_jinja2-0.4.0/ui/checkbox/checkbox.html.j2 +17 -0
- authfly_jinja2-0.4.0/ui/dialog/dialog.html.j2 +14 -0
- authfly_jinja2-0.4.0/ui/disclosure/disclosure.html.j2 +11 -0
- authfly_jinja2-0.4.0/ui/disclosure/summary.html.j2 +9 -0
- authfly_jinja2-0.4.0/ui/form/form.html.j2 +20 -0
- authfly_jinja2-0.4.0/ui/icon/icon.html.j2 +13 -0
- authfly_jinja2-0.4.0/ui/icon/icon.variants.json +19 -0
- authfly_jinja2-0.4.0/ui/image/image.html.j2 +16 -0
- authfly_jinja2-0.4.0/ui/image/picture.html.j2 +9 -0
- authfly_jinja2-0.4.0/ui/image/source.html.j2 +11 -0
- authfly_jinja2-0.4.0/ui/input/input.html.j2 +21 -0
- authfly_jinja2-0.4.0/ui/label/label.html.j2 +10 -0
- authfly_jinja2-0.4.0/ui/link/link.html.j2 +15 -0
- authfly_jinja2-0.4.0/ui/meter/meter.html.j2 +16 -0
- authfly_jinja2-0.4.0/ui/progress/progress.html.j2 +12 -0
- authfly_jinja2-0.4.0/ui/radio/radio.html.j2 +17 -0
- authfly_jinja2-0.4.0/ui/select/select-opt-group.html.j2 +10 -0
- authfly_jinja2-0.4.0/ui/select/select-option.html.j2 +11 -0
- authfly_jinja2-0.4.0/ui/select/select.html.j2 +20 -0
- authfly_jinja2-0.4.0/ui/separator/separator.html.j2 +10 -0
- authfly_jinja2-0.4.0/ui/switch/switch.html.j2 +19 -0
- authfly_jinja2-0.4.0/ui/text/text.html.j2 +9 -0
- authfly_jinja2-0.4.0/ui/textarea/textarea.html.j2 +17 -0
- authfly_jinja2-0.4.0/ui8kit_jinja/__init__.py +81 -0
- authfly_jinja2-0.4.0/ui8kit_jinja/classes.py +31 -0
- authfly_jinja2-0.4.0/ui8kit_jinja/cn_merge.py +142 -0
- 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
|