dj-design-system 0.1.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 (140) hide show
  1. dj_design_system-0.1.0/.gitignore +52 -0
  2. dj_design_system-0.1.0/CHANGELOG.md +23 -0
  3. dj_design_system-0.1.0/CONTRIBUTING.md +49 -0
  4. dj_design_system-0.1.0/LICENSE +21 -0
  5. dj_design_system-0.1.0/PKG-INFO +118 -0
  6. dj_design_system-0.1.0/README.md +72 -0
  7. dj_design_system-0.1.0/dj_design_system/__init__.py +1 -0
  8. dj_design_system-0.1.0/dj_design_system/apps.py +13 -0
  9. dj_design_system-0.1.0/dj_design_system/components.py +293 -0
  10. dj_design_system-0.1.0/dj_design_system/data.py +256 -0
  11. dj_design_system-0.1.0/dj_design_system/finders.py +89 -0
  12. dj_design_system-0.1.0/dj_design_system/forms.py +81 -0
  13. dj_design_system-0.1.0/dj_design_system/migrations/0001_initial.py +33 -0
  14. dj_design_system-0.1.0/dj_design_system/migrations/__init__.py +0 -0
  15. dj_design_system-0.1.0/dj_design_system/models.py +12 -0
  16. dj_design_system-0.1.0/dj_design_system/parameters/__init__.py +20 -0
  17. dj_design_system-0.1.0/dj_design_system/parameters/base.py +151 -0
  18. dj_design_system-0.1.0/dj_design_system/parameters/field.py +28 -0
  19. dj_design_system-0.1.0/dj_design_system/parameters/model.py +203 -0
  20. dj_design_system-0.1.0/dj_design_system/parameters/user.py +16 -0
  21. dj_design_system-0.1.0/dj_design_system/services/__init__.py +0 -0
  22. dj_design_system-0.1.0/dj_design_system/services/canvas.py +234 -0
  23. dj_design_system-0.1.0/dj_design_system/services/component.py +69 -0
  24. dj_design_system-0.1.0/dj_design_system/services/markdown_canvas.py +306 -0
  25. dj_design_system-0.1.0/dj_design_system/services/media.py +100 -0
  26. dj_design_system-0.1.0/dj_design_system/services/navigation.py +533 -0
  27. dj_design_system-0.1.0/dj_design_system/services/registry.py +259 -0
  28. dj_design_system-0.1.0/dj_design_system/services/tag_signature.py +497 -0
  29. dj_design_system-0.1.0/dj_design_system/settings.py +113 -0
  30. dj_design_system-0.1.0/dj_design_system/static/django_design_system/canvas.css +36 -0
  31. dj_design_system-0.1.0/dj_design_system/static/django_design_system/gallery-highlight.css +229 -0
  32. dj_design_system-0.1.0/dj_design_system/static/django_design_system/gallery-markdown.css +108 -0
  33. dj_design_system-0.1.0/dj_design_system/static/django_design_system/gallery-measure.js +185 -0
  34. dj_design_system-0.1.0/dj_design_system/static/django_design_system/gallery-search.js +284 -0
  35. dj_design_system-0.1.0/dj_design_system/static/django_design_system/gallery-toolbar.css +179 -0
  36. dj_design_system-0.1.0/dj_design_system/static/django_design_system/gallery-toolbar.js +610 -0
  37. dj_design_system-0.1.0/dj_design_system/static/django_design_system/gallery.css +1416 -0
  38. dj_design_system-0.1.0/dj_design_system/static/django_design_system/htmx.min.js +1 -0
  39. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/canvas/iframe.html +43 -0
  40. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/base.html +83 -0
  41. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/breadcrumb.html +53 -0
  42. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/component.html +232 -0
  43. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/documentation.html +22 -0
  44. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/folder.html +47 -0
  45. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/index.html +16 -0
  46. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/navtree.html +48 -0
  47. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/sandbox_fragment.html +47 -0
  48. dj_design_system-0.1.0/dj_design_system/templates/dj_design_system/gallery/toolbar.html +170 -0
  49. dj_design_system-0.1.0/dj_design_system/templatetags/__init__.py +0 -0
  50. dj_design_system-0.1.0/dj_design_system/templatetags/design_components.py +82 -0
  51. dj_design_system-0.1.0/dj_design_system/templatetags/dj_design_system_gallery.py +160 -0
  52. dj_design_system-0.1.0/dj_design_system/types.py +26 -0
  53. dj_design_system-0.1.0/dj_design_system/urls.py +30 -0
  54. dj_design_system-0.1.0/dj_design_system/views.py +438 -0
  55. dj_design_system-0.1.0/docs/api/components.md +15 -0
  56. dj_design_system-0.1.0/docs/api/parameters.md +15 -0
  57. dj_design_system-0.1.0/docs/api-reference.md +6 -0
  58. dj_design_system-0.1.0/docs/assets/docs-theme.css +284 -0
  59. dj_design_system-0.1.0/docs/components.md +356 -0
  60. dj_design_system-0.1.0/docs/gallery.md +349 -0
  61. dj_design_system-0.1.0/docs/index.md +83 -0
  62. dj_design_system-0.1.0/docs/organisation.md +158 -0
  63. dj_design_system-0.1.0/docs/quickstart.md +144 -0
  64. dj_design_system-0.1.0/docs/registry.md +87 -0
  65. dj_design_system-0.1.0/docs/templatetags.md +233 -0
  66. dj_design_system-0.1.0/example_project/__init__.py +0 -0
  67. dj_design_system-0.1.0/example_project/demo_components/__init__.py +0 -0
  68. dj_design_system-0.1.0/example_project/demo_components/apps.py +6 -0
  69. dj_design_system-0.1.0/example_project/demo_components/components/__init__.py +0 -0
  70. dj_design_system-0.1.0/example_project/demo_components/components/alert.py +28 -0
  71. dj_design_system-0.1.0/example_project/demo_components/components/badge.py +19 -0
  72. dj_design_system-0.1.0/example_project/demo_components/components/button/__init__.py +0 -0
  73. dj_design_system-0.1.0/example_project/demo_components/components/button/button.css +34 -0
  74. dj_design_system-0.1.0/example_project/demo_components/components/button/button.js +11 -0
  75. dj_design_system-0.1.0/example_project/demo_components/components/button/button.py +40 -0
  76. dj_design_system-0.1.0/example_project/demo_components/components/card/__init__.py +0 -0
  77. dj_design_system-0.1.0/example_project/demo_components/components/card/abstract_card.py +12 -0
  78. dj_design_system-0.1.0/example_project/demo_components/components/card/info_card.py +32 -0
  79. dj_design_system-0.1.0/example_project/demo_components/components/card/layouts/__init__.py +0 -0
  80. dj_design_system-0.1.0/example_project/demo_components/components/card/layouts/hero.py +19 -0
  81. dj_design_system-0.1.0/example_project/demo_components/components/rich_button.css +5 -0
  82. dj_design_system-0.1.0/example_project/demo_components/components/rich_button.py +26 -0
  83. dj_design_system-0.1.0/example_project/demo_components/components/user_card.py +34 -0
  84. dj_design_system-0.1.0/example_project/demo_extra/__init__.py +0 -0
  85. dj_design_system-0.1.0/example_project/demo_extra/apps.py +6 -0
  86. dj_design_system-0.1.0/example_project/demo_extra/components/__init__.py +0 -0
  87. dj_design_system-0.1.0/example_project/demo_extra/components/button.py +22 -0
  88. dj_design_system-0.1.0/example_project/demo_nav/__init__.py +0 -0
  89. dj_design_system-0.1.0/example_project/demo_nav/apps.py +6 -0
  90. dj_design_system-0.1.0/example_project/demo_nav/components/__init__.py +0 -0
  91. dj_design_system-0.1.0/example_project/demo_nav/components/button.py +17 -0
  92. dj_design_system-0.1.0/example_project/demo_nav/components/cards/__init__.py +0 -0
  93. dj_design_system-0.1.0/example_project/demo_nav/components/cards/info_card/__init__.py +0 -0
  94. dj_design_system-0.1.0/example_project/demo_nav/components/cards/info_card/info_card.py +12 -0
  95. dj_design_system-0.1.0/example_project/demo_nav/components/design_guidelines.md +15 -0
  96. dj_design_system-0.1.0/example_project/demo_nav/components/elements/__init__.py +0 -0
  97. dj_design_system-0.1.0/example_project/demo_nav/components/elements/badge.py +12 -0
  98. dj_design_system-0.1.0/example_project/demo_nav/components/elements/icon/__init__.py +0 -0
  99. dj_design_system-0.1.0/example_project/demo_nav/components/elements/icon/accessibility.md +19 -0
  100. dj_design_system-0.1.0/example_project/demo_nav/components/elements/icon/component.py +19 -0
  101. dj_design_system-0.1.0/example_project/demo_nav/components/elements/icon/index.md +14 -0
  102. dj_design_system-0.1.0/example_project/demo_nav/components/elements/index.md +6 -0
  103. dj_design_system-0.1.0/example_project/demo_nav/components/generic/__init__.py +0 -0
  104. dj_design_system-0.1.0/example_project/demo_nav/components/generic/divider.py +13 -0
  105. dj_design_system-0.1.0/example_project/demo_nav/components/generic/tooltip/__init__.py +0 -0
  106. dj_design_system-0.1.0/example_project/demo_nav/components/generic/tooltip/component.py +16 -0
  107. dj_design_system-0.1.0/example_project/demo_single/__init__.py +0 -0
  108. dj_design_system-0.1.0/example_project/demo_single/apps.py +6 -0
  109. dj_design_system-0.1.0/example_project/demo_single/components.py +42 -0
  110. dj_design_system-0.1.0/example_project/manage.py +22 -0
  111. dj_design_system-0.1.0/example_project/settings.py +84 -0
  112. dj_design_system-0.1.0/example_project/static/example_project/demo.css +300 -0
  113. dj_design_system-0.1.0/example_project/urls.py +8 -0
  114. dj_design_system-0.1.0/example_project/wsgi.py +8 -0
  115. dj_design_system-0.1.0/pyproject.toml +161 -0
  116. dj_design_system-0.1.0/tests/__init__.py +0 -0
  117. dj_design_system-0.1.0/tests/conftest.py +136 -0
  118. dj_design_system-0.1.0/tests/e2e/__init__.py +0 -0
  119. dj_design_system-0.1.0/tests/e2e/conftest.py +22 -0
  120. dj_design_system-0.1.0/tests/e2e/test_gallery.py +201 -0
  121. dj_design_system-0.1.0/tests/settings.py +110 -0
  122. dj_design_system-0.1.0/tests/test_abstract_component.py +79 -0
  123. dj_design_system-0.1.0/tests/test_canvas_service.py +200 -0
  124. dj_design_system-0.1.0/tests/test_canvas_templatetag.py +68 -0
  125. dj_design_system-0.1.0/tests/test_canvas_views.py +148 -0
  126. dj_design_system-0.1.0/tests/test_components.py +201 -0
  127. dj_design_system-0.1.0/tests/test_data.py +239 -0
  128. dj_design_system-0.1.0/tests/test_finders.py +140 -0
  129. dj_design_system-0.1.0/tests/test_forms.py +283 -0
  130. dj_design_system-0.1.0/tests/test_markdown_canvas.py +359 -0
  131. dj_design_system-0.1.0/tests/test_navigation.py +887 -0
  132. dj_design_system-0.1.0/tests/test_parameters.py +643 -0
  133. dj_design_system-0.1.0/tests/test_registry.py +573 -0
  134. dj_design_system-0.1.0/tests/test_search.py +317 -0
  135. dj_design_system-0.1.0/tests/test_settings.py +40 -0
  136. dj_design_system-0.1.0/tests/test_tag_signature.py +355 -0
  137. dj_design_system-0.1.0/tests/test_templatetags.py +321 -0
  138. dj_design_system-0.1.0/tests/test_types.py +112 -0
  139. dj_design_system-0.1.0/tests/test_views.py +603 -0
  140. dj_design_system-0.1.0/tests/urls.py +8 -0
@@ -0,0 +1,52 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.egg-info/
7
+ *.egg
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+ env/
13
+
14
+ # Tools
15
+ .mypy_cache/
16
+ .pytest_cache/
17
+ .ruff_cache/
18
+ .tox/
19
+
20
+ # Build outputs
21
+ dist/
22
+ build/
23
+ # mkdocs build output
24
+ site/
25
+
26
+ # Coverage
27
+ .coverage
28
+ .coverage.*
29
+ htmlcov/
30
+ coverage.xml
31
+
32
+ # Databases
33
+ *.db
34
+ *.sqlite3
35
+
36
+ # Secrets / local config
37
+ .env
38
+ .env.*
39
+ !.env.example
40
+
41
+ # OS
42
+ .DS_Store
43
+ Thumbs.db
44
+
45
+ # Editor
46
+ .vscode/
47
+ .idea/
48
+ *.swp
49
+ *.swo
50
+
51
+ # Static files collected by Django
52
+ staticfiles/
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.0.1] - unreleased
11
+
12
+ ### Added
13
+
14
+ - `TagComponent` and `BlockComponent` base classes for defining UI components
15
+ - Parameter system: `StrParam`, `BoolParam`, `StrCSSClassParam`, `BoolCSSClassParam`, `ModelParam`, `UserParam`, `FieldParam`
16
+ - Auto-discovery of components via Django's app registry (`AppConfig.ready()`)
17
+ - Component registry with lookup, listing, and navigation tree APIs
18
+ - Interactive gallery with live component previews in sandboxed iframes
19
+ - Searchable navigation tree with folder, component, and documentation node types
20
+ - Auto-generated templatetag usage examples from parameter definitions
21
+ - `ComponentsStaticFinder` to serve per-component CSS/JS via Django staticfiles
22
+ - Markdown documentation pages auto-discovered alongside components
23
+ - Configurable canvas backgrounds, toolbar, and gallery settings via `dj_design_system` Django settings dict
@@ -0,0 +1,49 @@
1
+ # Contributing to dj-design-system
2
+
3
+ Thank you for taking the time to contribute!
4
+
5
+ ## Setting up a development environment
6
+
7
+ **Prerequisites:** [uv](https://docs.astral.sh/uv/) and [just](https://just.systems/) must be installed.
8
+
9
+ ```bash
10
+ git clone https://github.com/Django-Design-System/django_design_system.git
11
+ cd django_design_system
12
+ just install
13
+ just install-hooks # installs the pre-commit git hook
14
+ just install-playwright # installs Playwright browsers (needed for e2e tests)
15
+ ```
16
+
17
+ ## Running things locally
18
+
19
+ | Command | Purpose |
20
+ | ------------------------- | --------------------------------------------------- |
21
+ | `just test` | Run all unit tests |
22
+ | `just test-one <pattern>` | Run tests matching a keyword |
23
+ | `just e2e` | Run Playwright end-to-end tests |
24
+ | `just coverage` | Unit tests with coverage report |
25
+ | `just check` | Lint and formatting check (no changes) |
26
+ | `just fix` | Auto-fix all lint and formatting issues |
27
+ | `just typecheck` | Run mypy type checking |
28
+ | `just docs-serve` | Serve the documentation site locally |
29
+ | `just demo` | Start the example component gallery in your browser |
30
+
31
+ The pre-commit hook installed by `just install-hooks` runs `just fix` automatically before every commit.
32
+
33
+ ## Code conventions
34
+
35
+ - **Python style** is enforced by [ruff](https://docs.astral.sh/ruff/) — run `just fix` before committing.
36
+ - **HTML/template style** is enforced by [djlint](https://djlint.com/) — also covered by `just fix`.
37
+ - **Type annotations** are checked by [mypy](https://mypy-lang.org/) — run `just typecheck`.
38
+ - Line length is 88 characters.
39
+ - All new public classes and functions should have docstrings — they feed directly into the auto-generated API documentation.
40
+
41
+ ## Submitting a pull request
42
+
43
+ 1. Fork the repository and create a branch from `main`.
44
+ 2. Make your changes, add tests for any new behaviour.
45
+ 3. Run `just test` and `just check` — both must pass.
46
+ 4. Open a PR against `main`. All CI checks must be green before merging.
47
+ 5. A maintainer will review and merge.
48
+
49
+ For bug reports or feature requests, please [open an issue](https://github.com/Django-Design-System/django_design_system/issues) first.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Department for Business and Trade
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,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: dj-design-system
3
+ Version: 0.1.0
4
+ Summary: A Design System for Django
5
+ Project-URL: Homepage, https://django-design-system.github.io/django_design_system/
6
+ Project-URL: Repository, https://github.com/Django-Design-System/django_design_system
7
+ Project-URL: Documentation, https://django-design-system.github.io/django_design_system/
8
+ Project-URL: Bug Tracker, https://github.com/Django-Design-System/django_design_system/issues
9
+ Project-URL: Changelog, https://github.com/Django-Design-System/django_design_system/blob/main/CHANGELOG.md
10
+ Maintainer: marcelkornblum, CamLamb
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: Design System,component,django
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Framework :: Django
16
+ Classifier: Framework :: Django :: 5.2
17
+ Classifier: Framework :: Django :: 6.0
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Documentation
24
+ Classifier: Topic :: Software Development :: User Interfaces
25
+ Requires-Python: >=3.13
26
+ Requires-Dist: django<8,>=5.2
27
+ Requires-Dist: markdown
28
+ Provides-Extra: dev
29
+ Requires-Dist: djlint; extra == 'dev'
30
+ Requires-Dist: mypy; extra == 'dev'
31
+ Requires-Dist: ruff; extra == 'dev'
32
+ Requires-Dist: types-markdown; extra == 'dev'
33
+ Requires-Dist: types-pygments; extra == 'dev'
34
+ Provides-Extra: docs
35
+ Requires-Dist: mkdocs-material; extra == 'docs'
36
+ Requires-Dist: mkdocstrings-python; extra == 'docs'
37
+ Provides-Extra: testing
38
+ Requires-Dist: coverage; extra == 'testing'
39
+ Requires-Dist: factory-boy; extra == 'testing'
40
+ Requires-Dist: playwright; extra == 'testing'
41
+ Requires-Dist: pytest-cov; extra == 'testing'
42
+ Requires-Dist: pytest-django; extra == 'testing'
43
+ Requires-Dist: pytest-mock; extra == 'testing'
44
+ Requires-Dist: pytest-playwright; extra == 'testing'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # dj-design-system
48
+
49
+ [![CI](https://github.com/Django-Design-System/django_design_system/actions/workflows/ci.yml/badge.svg)](https://github.com/Django-Design-System/django_design_system/actions/workflows/ci.yml)
50
+ [![PyPI version](https://img.shields.io/pypi/v/dj-design-system)](https://pypi.org/project/dj-design-system/)
51
+ [![Python versions](https://img.shields.io/pypi/pyversions/dj-design-system)](https://pypi.org/project/dj-design-system/)
52
+ [![Django](https://img.shields.io/badge/django-5.2%20%7C%206.0%20%7C%20latest-green)](https://www.djangoproject.com/)
53
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow)](LICENSE)
54
+
55
+ DjDS is a Django-native approach to writing front end components that are exposed as templatetags. It comes with an auto-generated, customisable, live interactive gallery of your UI components that lives alongside your Django project.
56
+
57
+ Components are recognisably Django elements; they look and work like Models or Forms. The gallery auto-discovers them, renders live previews in sandboxed iframes, generates templatetag usage examples, and builds a searchable navigation tree — all from your existing code and docstrings.
58
+
59
+ **[Browse the demo gallery →](https://django-design-system.github.io/django_design_system/gallery/)**
60
+
61
+ ## Quick start
62
+
63
+ ```bash
64
+ pip install dj-design-system
65
+ ```
66
+
67
+ Then follow the [quickstart guide](https://django-design-system.github.io/django_design_system/quickstart/) to register your first component.
68
+
69
+ ## Supported versions
70
+
71
+ | Python | Django |
72
+ | ------ | --------- |
73
+ | 3.13 | 5.2 (LTS) |
74
+ | 3.14 | 6.0 |
75
+ | | latest |
76
+
77
+ ## Documentation
78
+
79
+ Full documentation is available at **[django-design-system.github.io/django_design_system/](https://django-design-system.github.io/django_design_system/)**.
80
+
81
+ To browse locally:
82
+
83
+ ```bash
84
+ just docs-serve
85
+ ```
86
+
87
+ | Document | Contents |
88
+ | ------------------------------------- | ----------------------------------------- |
89
+ | [Quick start](docs/quickstart.md) | Installation and first component |
90
+ | [Components](docs/components.md) | Defining components and parameters |
91
+ | [Registry](docs/registry.md) | Auto-discovery and the component registry |
92
+ | [Gallery](docs/gallery.md) | Configuring and customising the gallery |
93
+ | [Template tags](docs/templatetags.md) | Using components in templates |
94
+ | [API reference](docs/api/) | Auto-generated class and parameter docs |
95
+
96
+ A static snapshot of the component gallery is browseable at **[django-design-system.github.io/django_design_system/gallery/](https://django-design-system.github.io/django_design_system/gallery/)** (HTMX interactions disabled; run `just demo` for the full live experience).
97
+
98
+ ## Contributing
99
+
100
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up a dev environment, run tests, and submit a pull request.
101
+
102
+ ## Code of conduct
103
+
104
+ This project follows the [Contributor Covenant](https://www.contributor-covenant.org/). Please read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before contributing.
105
+
106
+ ## Issues and feature requests
107
+
108
+ Please open an issue on [GitHub](https://github.com/Django-Design-System/django_design_system/issues). Use the bug report template for defects and the feature request template for new ideas.
109
+
110
+ ## Security
111
+
112
+ To report a vulnerability privately, see [SECURITY.md](SECURITY.md).
113
+
114
+ ## Licence
115
+
116
+ [MIT](LICENSE)
117
+
118
+ With thanks to the UK [Department for Business and Trade](https://github.com/uktrade), where this was originally developed.
@@ -0,0 +1,72 @@
1
+ # dj-design-system
2
+
3
+ [![CI](https://github.com/Django-Design-System/django_design_system/actions/workflows/ci.yml/badge.svg)](https://github.com/Django-Design-System/django_design_system/actions/workflows/ci.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/dj-design-system)](https://pypi.org/project/dj-design-system/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/dj-design-system)](https://pypi.org/project/dj-design-system/)
6
+ [![Django](https://img.shields.io/badge/django-5.2%20%7C%206.0%20%7C%20latest-green)](https://www.djangoproject.com/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow)](LICENSE)
8
+
9
+ DjDS is a Django-native approach to writing front end components that are exposed as templatetags. It comes with an auto-generated, customisable, live interactive gallery of your UI components that lives alongside your Django project.
10
+
11
+ Components are recognisably Django elements; they look and work like Models or Forms. The gallery auto-discovers them, renders live previews in sandboxed iframes, generates templatetag usage examples, and builds a searchable navigation tree — all from your existing code and docstrings.
12
+
13
+ **[Browse the demo gallery →](https://django-design-system.github.io/django_design_system/gallery/)**
14
+
15
+ ## Quick start
16
+
17
+ ```bash
18
+ pip install dj-design-system
19
+ ```
20
+
21
+ Then follow the [quickstart guide](https://django-design-system.github.io/django_design_system/quickstart/) to register your first component.
22
+
23
+ ## Supported versions
24
+
25
+ | Python | Django |
26
+ | ------ | --------- |
27
+ | 3.13 | 5.2 (LTS) |
28
+ | 3.14 | 6.0 |
29
+ | | latest |
30
+
31
+ ## Documentation
32
+
33
+ Full documentation is available at **[django-design-system.github.io/django_design_system/](https://django-design-system.github.io/django_design_system/)**.
34
+
35
+ To browse locally:
36
+
37
+ ```bash
38
+ just docs-serve
39
+ ```
40
+
41
+ | Document | Contents |
42
+ | ------------------------------------- | ----------------------------------------- |
43
+ | [Quick start](docs/quickstart.md) | Installation and first component |
44
+ | [Components](docs/components.md) | Defining components and parameters |
45
+ | [Registry](docs/registry.md) | Auto-discovery and the component registry |
46
+ | [Gallery](docs/gallery.md) | Configuring and customising the gallery |
47
+ | [Template tags](docs/templatetags.md) | Using components in templates |
48
+ | [API reference](docs/api/) | Auto-generated class and parameter docs |
49
+
50
+ A static snapshot of the component gallery is browseable at **[django-design-system.github.io/django_design_system/gallery/](https://django-design-system.github.io/django_design_system/gallery/)** (HTMX interactions disabled; run `just demo` for the full live experience).
51
+
52
+ ## Contributing
53
+
54
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up a dev environment, run tests, and submit a pull request.
55
+
56
+ ## Code of conduct
57
+
58
+ This project follows the [Contributor Covenant](https://www.contributor-covenant.org/). Please read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before contributing.
59
+
60
+ ## Issues and feature requests
61
+
62
+ Please open an issue on [GitHub](https://github.com/Django-Design-System/django_design_system/issues). Use the bug report template for defects and the feature request template for new ideas.
63
+
64
+ ## Security
65
+
66
+ To report a vulnerability privately, see [SECURITY.md](SECURITY.md).
67
+
68
+ ## Licence
69
+
70
+ [MIT](LICENSE)
71
+
72
+ With thanks to the UK [Department for Business and Trade](https://github.com/uktrade), where this was originally developed.
@@ -0,0 +1 @@
1
+ from dj_design_system.services.registry import component_registry # noqa: F401
@@ -0,0 +1,13 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjangoDesignSystemConfig(AppConfig):
5
+ """App configuration for dj_design_system."""
6
+
7
+ name = "dj_design_system"
8
+ default_auto_field = "django.db.models.BigAutoField"
9
+
10
+ def ready(self) -> None:
11
+ from dj_design_system import component_registry
12
+
13
+ component_registry.autodiscover()
@@ -0,0 +1,293 @@
1
+ from typing import TYPE_CHECKING, Any
2
+
3
+ from django.utils.html import format_html
4
+ from django.utils.safestring import SafeString
5
+
6
+ from dj_design_system.parameters import BaseParam
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ from dj_design_system.data import ComponentMedia
11
+
12
+
13
+ class BaseComponent:
14
+ # The template string itself to pass to format_html. Should be overridden by subclasses.
15
+ template_format_str: str = "<span class='{classes}'>ABSTRACT COMPONENT</span>"
16
+
17
+ class Meta:
18
+ abstract = True
19
+
20
+ def __init_subclass__(cls, **kwargs) -> None:
21
+ """Validate Meta constraint declarations at class definition time."""
22
+ super().__init_subclass__(**kwargs)
23
+ from dj_design_system.services.component import get_own_meta, is_abstract
24
+
25
+ if is_abstract(cls):
26
+ return
27
+
28
+ meta = get_own_meta(cls)
29
+ param_names = set(cls.get_params().keys())
30
+
31
+ for a, b in getattr(meta, "mutually_exclusive", []):
32
+ for name in (a, b):
33
+ if name not in param_names:
34
+ raise ValueError(
35
+ f"{cls.__name__}.Meta.mutually_exclusive references unknown param '{name}'."
36
+ )
37
+
38
+ for dependent, dependency in getattr(meta, "requires", []):
39
+ for name in (dependent, dependency):
40
+ if name not in param_names:
41
+ raise ValueError(
42
+ f"{cls.__name__}.Meta.requires references unknown param '{name}'."
43
+ )
44
+
45
+ def __init__(self, **kwargs):
46
+ self.context = {}
47
+ for var_name, var_value in kwargs.items():
48
+ setattr(self, var_name, var_value)
49
+
50
+ self._validate_meta_constraints()
51
+ self.validate_params()
52
+
53
+ def validate_params(self) -> None:
54
+ """An override hook allowing param combinations or values to raise exceptions if necessary"""
55
+ ...
56
+
57
+ def _validate_meta_constraints(self) -> None:
58
+ """Enforce mutually_exclusive and requires constraints declared on Meta.
59
+
60
+ Called automatically during __init__ before validate_params.
61
+ - Meta.mutually_exclusive: list of (param_a, param_b) pairs that cannot both be set.
62
+ - Meta.requires: list of (dependent, dependency) pairs where setting dependent
63
+ requires dependency to also be set.
64
+ """
65
+ from dj_design_system.services.component import get_own_meta
66
+
67
+ params = type(self).get_params()
68
+ meta = get_own_meta(type(self))
69
+
70
+ for a, b in getattr(meta, "mutually_exclusive", []):
71
+ if params[a].has_been_set(self) and params[b].has_been_set(self):
72
+ raise ValueError(
73
+ f"'{a}' and '{b}' cannot both be set on {type(self).__name__}."
74
+ )
75
+
76
+ for dependent, dependency in getattr(meta, "requires", []):
77
+ if params[dependent].has_been_set(self) and not params[
78
+ dependency
79
+ ].has_been_set(self):
80
+ raise ValueError(
81
+ f"'{dependent}' requires '{dependency}' to also be set on {type(self).__name__}."
82
+ )
83
+
84
+ def get_context(self) -> dict[str, Any]:
85
+ """
86
+ Get the context for rendering the component. This method can be overridden by subclasses to add additional or edit other context variables.
87
+ """
88
+ self.context["classes"] = self.get_classes_string()
89
+ for param_name, spec in self.params.items():
90
+ value = getattr(self, param_name)
91
+ self.context[param_name] = value
92
+ self.context.update(spec.get_extra_context(param_name, value))
93
+ return self.context
94
+
95
+ def get_classes_string(self):
96
+ """
97
+ Get a string of CSS classes based on the context. This can be used in the template to apply conditional styling.
98
+ """
99
+ classes = []
100
+ for param_name, spec in self.params.items():
101
+ param_value = getattr(self, param_name)
102
+ classes.extend(spec.get_css_classes(param_name, param_value))
103
+ return " ".join(classes)
104
+
105
+ def render(self) -> str:
106
+ """
107
+ Render the component as an HTML string.
108
+ """
109
+ return format_html(format_string=self.template_format_str, **self.get_context())
110
+
111
+ def __str__(self) -> str:
112
+ return self.render()
113
+
114
+ def __html__(self) -> str:
115
+ return self.render()
116
+
117
+ @property
118
+ def description(self) -> str | None:
119
+ return self.__doc__
120
+
121
+ @property
122
+ def params(self) -> dict[str, "BaseParam"]:
123
+ return type(self).get_params()
124
+
125
+ @classmethod
126
+ def get_params(cls) -> dict[str, "BaseParam"]:
127
+ """
128
+ Get the parameters for this component. Returns a dictionary of all
129
+ BaseParam descriptors defined on the class (or any subclass in the MRO).
130
+ """
131
+ result = {}
132
+ for klass in cls.__mro__:
133
+ for attr_name, attr_value in vars(klass).items():
134
+ if isinstance(attr_value, BaseParam) and attr_name not in result:
135
+ result[attr_name] = attr_value
136
+ return result
137
+
138
+ @classmethod
139
+ def docstring(cls) -> str:
140
+ """
141
+ Return a string describing the API of this component, including its parameters and their types.
142
+ """
143
+ params = cls.get_params()
144
+ api_docs = f"{cls.__doc__}\n\n"
145
+ if len(params) > 0:
146
+ api_docs += "Parameters:\n"
147
+ for parameter_spec in params.values():
148
+ api_docs += f"- {parameter_spec.docstring()}\n"
149
+ return api_docs
150
+
151
+ @classmethod
152
+ def get_name(cls) -> str:
153
+ """Return the component's registered name from the registry."""
154
+ from dj_design_system import component_registry
155
+
156
+ return component_registry.get_info(cls).name
157
+
158
+ @classmethod
159
+ def get_app_label(cls) -> str:
160
+ """Return the app label this component was discovered in."""
161
+ from dj_design_system import component_registry
162
+
163
+ return component_registry.get_info(cls).app_label
164
+
165
+ @classmethod
166
+ def get_relative_path(cls) -> str:
167
+ """Return the relative path within the app's components directory."""
168
+ from dj_design_system import component_registry
169
+
170
+ return component_registry.get_info(cls).relative_path
171
+
172
+ @classmethod
173
+ def get_media(cls) -> "ComponentMedia":
174
+ """Return the CSS and JS static URL paths required by this component.
175
+
176
+ Delegates to ``ComponentInfo.media`` — see that property for full
177
+ documentation of auto-discovery and ``Media`` class override behaviour.
178
+ """
179
+ from dj_design_system import component_registry
180
+
181
+ return component_registry.get_info(cls).media
182
+
183
+ @classmethod
184
+ def get_positional_args(cls) -> list[str]:
185
+ """Return the list of positional arg names from the class's own Meta.positional_args.
186
+
187
+ Only looks at the class's own ``Meta`` — positional_args are NOT
188
+ inherited from parent classes, matching Django's convention that
189
+ Meta is not inherited. This avoids silent ordering surprises when
190
+ subclassing.
191
+ """
192
+ from dj_design_system.services.component import get_own_meta
193
+
194
+ meta = get_own_meta(cls)
195
+ positional = getattr(meta, "positional_args", None)
196
+ return list(positional) if positional else []
197
+
198
+ @staticmethod
199
+ def map_positional_args(
200
+ positional_args: list[str], args: tuple, kwargs: dict
201
+ ) -> dict:
202
+ """Map positional arguments to keyword arguments using the positional_args spec."""
203
+ for i, arg_name in enumerate(positional_args):
204
+ if i < len(args):
205
+ kwargs[arg_name] = args[i]
206
+ return kwargs
207
+
208
+
209
+ class TagComponent(BaseComponent):
210
+ """
211
+ A component registered as a Django ``simple_tag``.
212
+
213
+ Subclass this for components that produce a single HTML fragment
214
+ without wrapping nested template content.
215
+
216
+ Use ``Meta.positional_args`` to declare parameters that can be passed
217
+ as positional arguments in the template tag::
218
+
219
+ class IconComponent(TagComponent):
220
+ name = StrParam("The icon name.")
221
+
222
+ class Meta:
223
+ positional_args = ["name"]
224
+
225
+ This allows ``{% icon "check" %}`` instead of ``{% icon name="check" %}``.
226
+ """
227
+
228
+ class Meta:
229
+ abstract = True
230
+
231
+ @classmethod
232
+ def as_tag(cls):
233
+ """Return a template tag function mapping positional args via Meta.positional_args."""
234
+ positional_args = cls.get_positional_args()
235
+
236
+ def _tag(*args, **kwargs):
237
+ cls.map_positional_args(positional_args, args, kwargs)
238
+ return cls(**kwargs)
239
+
240
+ return _tag
241
+
242
+
243
+ class BlockComponent(BaseComponent):
244
+ """
245
+ A component registered as a Django ``simple_block_tag``, allowing
246
+ for nested template content.
247
+
248
+ The template should include a ``{content}`` placeholder where the
249
+ inner content will be rendered. ``content`` is always the first
250
+ positional argument and should NOT appear in ``Meta.positional_args``.
251
+
252
+ Use ``Meta.positional_args`` to declare additional positional args
253
+ beyond ``content``::
254
+
255
+ class SectionComponent(BlockComponent):
256
+ title = StrParam("Section title.")
257
+
258
+ class Meta:
259
+ positional_args = ["title"]
260
+
261
+ This allows ``{% section "My Title" %}...{% endsection %}``.
262
+ """
263
+
264
+ class Meta:
265
+ abstract = True
266
+
267
+ template_format_str: str = "<span class='{classes}'>{content}</span>"
268
+
269
+ def __init__(self, content: SafeString, **kwargs):
270
+ self.content = content
271
+ super().__init__(**kwargs)
272
+
273
+ def get_context(self) -> dict[str, Any]:
274
+ """Add ``content`` to the context automatically.
275
+
276
+ ``content`` is the block body passed by the template engine — it is
277
+ NOT a declared BaseParam and should not appear in ``Meta.positional_args``
278
+ or ``docstring()`` output.
279
+ """
280
+ context = super().get_context()
281
+ context["content"] = self.content
282
+ return context
283
+
284
+ @classmethod
285
+ def as_tag(cls):
286
+ """Return a template tag function with content as first arg, plus any Meta.positional_args."""
287
+ positional_args = cls.get_positional_args()
288
+
289
+ def _tag(content, *args, **kwargs):
290
+ cls.map_positional_args(positional_args, args, kwargs)
291
+ return cls(content=content, **kwargs)
292
+
293
+ return _tag