├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── renovate.json5 ├── repository-social-media.png ├── storybook-django-logo.svg ├── storybook-django-screenshot.png └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.toml ├── CHANGELOG.md ├── LICENSE ├── Procfile ├── README.md ├── demo ├── __init__.py ├── core │ ├── jinja │ │ └── patterns_jinja │ │ │ └── jinja_components │ │ │ └── quote_block │ │ │ ├── quote_block.html │ │ │ └── quote_block.yaml │ ├── templates │ │ └── patterns │ │ │ ├── base.html │ │ │ ├── components │ │ │ ├── InfoBox │ │ │ │ ├── InfoBox.scss │ │ │ │ ├── InfoBox.stories.js │ │ │ │ └── InfoBox.tsx │ │ │ ├── LoadingIndicator │ │ │ │ ├── LoadingIndicator.scss │ │ │ │ ├── LoadingIndicator.stories.js │ │ │ │ └── LoadingIndicator.tsx │ │ │ ├── accordion │ │ │ │ ├── accordion.html │ │ │ │ ├── accordion.js │ │ │ │ ├── accordion.scss │ │ │ │ ├── accordion.stories.tsx │ │ │ │ ├── accordion.test.ts │ │ │ │ ├── accordion.yaml │ │ │ │ ├── accordion_section.html │ │ │ │ └── accordion_section.yaml │ │ │ ├── banner │ │ │ │ ├── home_banner.html │ │ │ │ ├── home_banner.stories.tsx │ │ │ │ ├── home_banner.yaml │ │ │ │ ├── split_banner.html │ │ │ │ ├── split_banner.scss │ │ │ │ ├── split_banner.stories.js │ │ │ │ └── split_banner.yaml │ │ │ ├── button │ │ │ │ ├── Button.stories.js │ │ │ │ ├── Button.tsx │ │ │ │ ├── button.html │ │ │ │ ├── button.scss │ │ │ │ └── button.yaml │ │ │ ├── cta │ │ │ │ ├── call_to_action.html │ │ │ │ ├── call_to_action.scss │ │ │ │ ├── call_to_action.yaml │ │ │ │ ├── cta_section.html │ │ │ │ ├── cta_section.scss │ │ │ │ └── cta_section.yaml │ │ │ ├── customers │ │ │ │ ├── customer-detail.html │ │ │ │ ├── customer-detail.scss │ │ │ │ ├── customer-detail.yaml │ │ │ │ ├── customers-header.html │ │ │ │ ├── customers-header.scss │ │ │ │ ├── customers-header.stories.js │ │ │ │ ├── customers-table.html │ │ │ │ ├── customers-table.scss │ │ │ │ ├── customers-table.stories.js │ │ │ │ └── customers-table.yaml │ │ │ ├── icon │ │ │ │ ├── Icon.stories.js │ │ │ │ ├── Icon.test.js │ │ │ │ ├── Icon.tsx │ │ │ │ ├── icon.html │ │ │ │ └── icon.scss │ │ │ ├── pagination │ │ │ │ ├── next_stage_link.html │ │ │ │ ├── next_stage_link.scss │ │ │ │ ├── next_stage_link.yaml │ │ │ │ ├── pagination.html │ │ │ │ └── pagination.yaml │ │ │ └── streamfield │ │ │ │ ├── call_to_action_block.html │ │ │ │ ├── call_to_action_block.yaml │ │ │ │ ├── casestudy_teaser_block.html │ │ │ │ ├── casestudy_teaser_block.scss │ │ │ │ ├── casestudy_teaser_block.yaml │ │ │ │ ├── heading_block.html │ │ │ │ ├── heading_block.stories.js │ │ │ │ ├── heading_block.yaml │ │ │ │ ├── pop │ │ │ │ ├── related_link.html │ │ │ │ ├── related_link.yaml │ │ │ │ ├── related_links.html │ │ │ │ └── related_links.yaml │ │ │ │ ├── quote_block.html │ │ │ │ ├── quote_block.md │ │ │ │ ├── quote_block.scss │ │ │ │ ├── quote_block.stories.js │ │ │ │ └── quote_block.yaml │ │ │ └── sprites │ │ │ └── sprites.html │ └── templatetags │ │ ├── wagtailcore_tags.py │ │ └── wagtailimages_tags.py ├── jinja2.py ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon.ico │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── settings.py ├── static_src │ ├── main.js │ ├── main.scss │ ├── sass │ │ ├── abstracts │ │ │ ├── _functions.scss │ │ │ ├── _mixins.scss │ │ │ └── _variables.scss │ │ ├── base │ │ │ ├── _base.scss │ │ │ ├── _section.scss │ │ │ ├── _typography.scss │ │ │ └── _wrapper.scss │ │ ├── utilities │ │ │ └── _utilities.scss │ │ └── vendor │ │ │ └── _normalize.scss │ └── tests │ │ ├── __image_snapshots__ │ │ └── .gitignore │ │ ├── __snapshots__ │ │ └── storyshots-snapshot.test.js.snap │ │ ├── assetMock.js │ │ ├── environment.js │ │ ├── setupTests.js │ │ ├── storyshots-axe.test.js │ │ ├── storyshots-image-snapshot.test.js │ │ └── storyshots-snapshot.test.js ├── storybook │ ├── Welcome.stories.mdx │ ├── __init__.py │ ├── main.js │ ├── manager.js │ ├── middleware.js │ ├── preview-head.html │ ├── preview.js │ ├── stories.d.ts │ ├── theme.js │ ├── urls.py │ └── yaml.stories.js ├── urls.py └── wsgi.py ├── docs ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── jest.config.js ├── manage.py ├── package-lock.json ├── package.json ├── requirements.txt ├── src ├── middleware.js ├── react.js └── storybook-django.js ├── tests └── storybook-django.test.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{js,ts,tsx,json,yml,yaml,md,css,scss}] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | static_compiled 4 | storybook_compiled 5 | venv 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See https://github.com/torchbox/eslint-config-torchbox for rules. 3 | extends: ['torchbox/typescript', 'plugin:react/jsx-runtime'], 4 | rules: { 5 | 'import/no-extraneous-dependencies': [ 6 | 2, 7 | { 8 | devDependencies: true, 9 | optionalDependencies: false, 10 | }, 11 | ], 12 | }, 13 | overrides: [ 14 | { 15 | files: ['*.stories.*', 'storybook'], 16 | rules: { 17 | 'react/jsx-props-no-spreading': 'off', 18 | }, 19 | }, 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '🐞 Bug report' 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | 18 | 19 | ### Describe the bug 20 | 21 | (Write your answer here.) 22 | 23 | ### Which terms did you search for in the documentation and issue tracker? 24 | 25 | 31 | 32 | (Write your answer here if relevant.) 33 | 34 | ### Environment 35 | 36 | 40 | 41 | (Write your answer here if relevant.) 42 | 43 | ### Steps to reproduce 44 | 45 | 49 | 50 | (Write your steps here:) 51 | 52 | 1. First, 53 | 2. Then, 54 | 3. Finally, 55 | 56 | ### Expected behavior 57 | 58 | 63 | 64 | (Write what you thought would happen.) 65 | 66 | ### Actual behavior 67 | 68 | 73 | 74 | (Write what happened. Please add screenshots!) 75 | 76 | ### Reproducible demo 77 | 78 | 92 | 93 | (Paste the link to an example project and exact instructions to reproduce the issue.) 94 | 95 | 107 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: '❓ Questions' 3 | url: https://github.com/torchbox/storybook-django/discussions 4 | about: Use GitHub Discussions to get help with this project. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '🚀 Feature request' 3 | about: Suggest an idea for improving this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ### Is your proposal related to a problem? 10 | 11 | 15 | 16 | (Write your answer here.) 17 | 18 | ### Describe the solution you’d like 19 | 20 | 23 | 24 | (Describe your proposed solution here.) 25 | 26 | ### Describe alternatives you’ve considered 27 | 28 | 31 | 32 | (Write your answer here.) 33 | 34 | ### Additional context 35 | 36 | 40 | 41 | (Write your answer here.) 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Here, please add a description of your pull request and instructions for the reviewer to verify your work. Pull requests without an adequate description will not be reviewed until one is added. 4 | 5 | 6 | 7 | --- 8 | 9 | 10 | 11 | - [ ] Stay on point and keep it small so it can be easily reviewed. For example, try to apply any general refactoring separately outside of the PR. 12 | - [ ] Consider adding unit tests, especially for bug fixes. If you don't, tell us why. 13 | - [ ] All new and existing tests pass. 14 | - [ ] Linting passes. 15 | - [ ] Consider updating documentation. If you don't, tell us why. 16 | - [ ] List the environments / platforms in which you tested your changes. 17 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | extends: ['config:base'], 3 | // https://renovatebot.com/docs/configuration-options/#commitbodytable 4 | commitBodyTable: true, 5 | // https://renovatebot.com/docs/configuration-options/#ignoredeps 6 | ignoreDeps: [], 7 | // https://renovatebot.com/docs/configuration-options/#labels 8 | labels: ['enhancement'], 9 | // https://renovatebot.com/docs/configuration-options/#prcreation 10 | prCreation: 'not-pending', 11 | // https://renovatebot.com/docs/configuration-options/#semanticcommits 12 | semanticCommits: false, 13 | // Use shorter commit messages to account for long dependency names. 14 | // https://docs.renovatebot.com/configuration-options/#commitmessagetopic 15 | commitMessageTopic: '{{depName}}', 16 | // https://renovatebot.com/docs/configuration-options/#prbodydefinitions 17 | prBodyDefinitions: {}, 18 | // https://renovatebot.com/docs/configuration-options/#prbodycolumns 19 | prBodyColumns: ['Package', 'Update', 'Type', 'Change'], 20 | // https://renovatebot.com/docs/configuration-options/#rebasestaleprs 21 | rebaseStalePrs: true, 22 | // https://renovatebot.com/docs/configuration-options/#schedule 23 | schedule: ['every weekend'], 24 | node: { 25 | major: { 26 | enabled: true, 27 | }, 28 | // https://renovatebot.com/docs/node/#configuring-support-policy 29 | supportPolicy: ['current'], 30 | }, 31 | packageRules: [ 32 | { 33 | packageNames: ['prettier'], 34 | groupName: 'prettier', 35 | automerge: true, 36 | }, 37 | ], 38 | } 39 | -------------------------------------------------------------------------------- /.github/repository-social-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torchbox/storybook-django/0b9314eb9711cbb782b4682e6d7fb5fe73e8cc62/.github/repository-social-media.png -------------------------------------------------------------------------------- /.github/storybook-django-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/storybook-django-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torchbox/storybook-django/0b9314eb9711cbb782b4682e6d7fb5fe73e8cc62/.github/storybook-django-screenshot.png -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'renovate/**' 8 | pull_request: 9 | 10 | jobs: 11 | test_python: 12 | runs-on: ubuntu-latest 13 | env: 14 | DJANGO_SETTINGS_MODULE: demo.settings 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-python@v2 18 | with: 19 | python-version: '3.10' 20 | - id: pip-cache 21 | uses: actions/cache@v2 22 | with: 23 | path: .venv 24 | key: ${{ runner.os }}-pip-v2-${{ hashFiles('**/runtime.txt') }}-${{ hashFiles('**/requirements.txt') }} 25 | - if: steps.pip-cache.outputs.cache-hit != 'true' 26 | run: python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt 27 | - run: source .venv/bin/activate && flake8 demo 28 | - run: source .venv/bin/activate && black demo --check 29 | - run: source .venv/bin/activate && python manage.py check 30 | - run: source .venv/bin/activate && python manage.py render_patterns 31 | test_node: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v3 35 | - uses: actions/setup-node@v3 36 | with: 37 | node-version-file: '.nvmrc' 38 | - id: node-cache 39 | uses: actions/cache@v2 40 | with: 41 | path: node_modules 42 | key: ${{ runner.os }}-node-${{ hashFiles('**/.nvmrc') }}-${{ hashFiles('**/package-lock.json') }} 43 | - if: steps.node-cache.outputs.cache-hit != 'true' 44 | run: npm ci --no-audit 45 | - run: npm run lint:js 46 | - run: npm run lint:format 47 | - run: npm run build 48 | - uses: actions/upload-artifact@v3 49 | with: 50 | name: storybook_compiled 51 | path: demo/storybook_compiled 52 | retention-days: 1 53 | test_storyshots: 54 | runs-on: ubuntu-latest 55 | needs: [test_python, test_node] 56 | steps: 57 | - uses: actions/checkout@v3 58 | - uses: actions/setup-node@v3 59 | with: 60 | node-version-file: '.nvmrc' 61 | - uses: actions/setup-python@v2 62 | with: 63 | python-version: '3.10' 64 | - id: node-cache 65 | uses: actions/cache@v2 66 | with: 67 | path: node_modules 68 | key: ${{ runner.os }}-node-${{ hashFiles('**/.nvmrc') }}-${{ hashFiles('**/package-lock.json') }} 69 | - id: pip-cache 70 | uses: actions/cache@v2 71 | with: 72 | path: .venv 73 | key: ${{ runner.os }}-pip-v2-${{ hashFiles('**/runtime.txt') }}-${{ hashFiles('**/requirements.txt') }} 74 | - uses: actions/download-artifact@v3 75 | with: 76 | name: storybook_compiled 77 | path: demo/storybook_compiled 78 | - run: source .venv/bin/activate && python manage.py runserver 0:8001 & 79 | - run: npm run test 80 | - uses: actions/upload-artifact@v3 81 | with: 82 | name: image_snapshots 83 | path: demo/static_src/tests/__image_snapshots__ 84 | retention-days: 30 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # ------------------------------------------------- 4 | # OS files 5 | # ------------------------------------------------- 6 | .DS_Store 7 | .DS_Store? 8 | ._* 9 | .Spotlight-V100 10 | .Trashes 11 | ehthumbs.db 12 | Thumbs.db 13 | 14 | # ------------------------------------------------- 15 | # Logs and databases 16 | # ------------------------------------------------- 17 | logs 18 | *.log 19 | npm-debug.log* 20 | *.sql 21 | *.sqlite3 22 | 23 | # ------------------------------------------------- 24 | # Runtime data and caches 25 | # ------------------------------------------------- 26 | pids 27 | *.pid 28 | *.seed 29 | *.pyc 30 | *.pyo 31 | *.pot 32 | 33 | # ------------------------------------------------- 34 | # Instrumentation and tooling 35 | # ------------------------------------------------- 36 | lib-cov 37 | coverage 38 | .coverage 39 | coverage_html_report 40 | .grunt 41 | .bundle 42 | webpack-stats.json 43 | webpack-stats.html 44 | 45 | # ------------------------------------------------- 46 | # Dependency directories 47 | # ------------------------------------------------- 48 | node_modules* 49 | python_modules* 50 | bower_components 51 | .venv 52 | venv 53 | .tox 54 | $virtualenv.tar.gz 55 | $node_modules.tar.gz 56 | 57 | # ------------------------------------------------- 58 | # Users Environment 59 | # ------------------------------------------------- 60 | .lock-wscript 61 | .idea 62 | .installed.cfg 63 | .vagrant 64 | .anaconda 65 | Vagrantfile.local 66 | .env 67 | /local 68 | local.py 69 | *.sublime-project 70 | *.sublime-workspace 71 | .vscode 72 | 73 | # ------------------------------------------------- 74 | # Generated files 75 | # ------------------------------------------------- 76 | dist 77 | build 78 | /var/static/ 79 | /var/media/ 80 | /docs/_build/ 81 | develop-eggs 82 | *.egg-info 83 | downloads 84 | media 85 | eggs 86 | parts 87 | lib64 88 | .sass-cache 89 | .mypy_cache 90 | 91 | # ------------------------------------------------- 92 | # Your own project's ignores 93 | # ------------------------------------------------- 94 | storybook_compiled 95 | static_compiled 96 | dpl-rendered-patterns 97 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | static_compiled 2 | storybook_compiled 3 | *.html 4 | coverage 5 | venv 6 | .venv 7 | backstop_data 8 | -------------------------------------------------------------------------------- /.prettierrc.toml: -------------------------------------------------------------------------------- 1 | # See https://prettier.io/docs/en/options.html. 2 | # Prettier also reads .editorconfig. 3 | printWidth = 80 4 | singleQuote = true 5 | quoteProps = 'consistent' 6 | trailingComma = 'all' 7 | arrowParens = 'always' 8 | proseWrap = 'preserve' 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | > All notable changes to this project are documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## Unreleased 6 | 7 | ### Changed 8 | 9 | - Upgrade to http-proxy-middleware v3 10 | 11 | ## [[v1.0.0]](https://github.com/torchbox/storybook-django/releases/tag/v1.0.0) (2024-07-27) 12 | 13 | ### Added 14 | 15 | - Pattern library API requests done with the `fetch` API now [include credentials](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#including_credentials) for same-origin requests. Thank you to [@jkevingutierrez](https://github.com/jkevingutierrez) ([#12](https://github.com/torchbox/storybook-django/pull/12)). 16 | 17 | ## [[v0.5.1]](https://github.com/torchbox/storybook-django/releases/tag/v0.5.1) (2022-04-06) 18 | 19 | ### Fixed 20 | 21 | - Fix comment expansion being too greedy 22 | 23 | ## [[v0.5.0]](https://github.com/torchbox/storybook-django/releases/tag/v0.5.0) (2022-04-06) 24 | 25 | ### Fixed 26 | 27 | - Change path imports for middleware and React code rather than using Node `exports` mapping. 28 | 29 | ### Added 30 | 31 | - Add JSDoc-based TypeScript typing for the public API. 32 | - Add a `data-testid="storybook-django"` attribute to facilitate automated testing. 33 | - Add a `data-state="empty|loading|loaded"` attribute to run code once templates are loaded. 34 | - Add a `data-template="path/to/template/component.html"` attribute to simplify troubleshooting. 35 | 36 | ## [[v0.4.1]](https://github.com/torchbox/storybook-django/releases/tag/v0.4.1) (2022-04-05) 37 | 38 | ### Fixed 39 | 40 | - Use Node [exports](https://nodejs.org/api/packages.html#subpath-exports) to export correct paths in published package. 41 | 42 | ## [[v0.4.0]](https://github.com/torchbox/storybook-django/releases/tag/v0.4.0) (2022-04-05) 43 | 44 | ### Added 45 | 46 | - Documentation for project basic setup and usage, and optional Webpack integrations. 47 | - Documentation for advanced usage: TypeScript, Storyshots. 48 | - Add new optional React API as `storybook-django/src/react` module. 49 | - Document expected usage with Vue (WIP). 50 | - Add experimental documentation generation capabilities. 51 | 52 | ### Fixed 53 | 54 | - Fix API client forcing tags to explicit null 55 | 56 | ## [[v0.3.0]](https://github.com/torchbox/storybook-django/releases/tag/v0.3.0) (2022-02-16) 57 | 58 | ### Changed 59 | 60 | - Move `http-proxy-middleware` to be a dependency of this package. 61 | - Change public API to be completely framework-agnostic. 62 | 63 | ## [[v0.2.0]](https://github.com/torchbox/storybook-django/releases/tag/v0.2.0) (2020-03-12) 64 | 65 | ### Added 66 | 67 | - Add basic error handling for TemplatePattern 68 | - Support defining pattern library API path globally instead of per-component 69 | - Add simpler stories definition API 70 | 71 | ### Changed 72 | 73 | - Rewrite library code to be consumable without a React compilation step 74 | 75 | ## [[v0.1.0]](https://github.com/torchbox/storybook-django/releases/tag/v0.1.0) (2020-02-21) 76 | 77 | First (barely) usable release! 78 | 79 | --- 80 | 81 | Template from http://keepachangelog.com/ 82 | 83 | ## [[vx.y.z]](https://github.com/torchbox/storybook-django/releases/tag/vx.y.z) 84 | 85 | ### Added 86 | 87 | - Something was added to the API / a new feature was introduced. 88 | 89 | ### Changed 90 | 91 | ### Fixed 92 | 93 | ### Removed 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-current Torchbox Ltd 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. 22 | 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn demo.wsgi 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Storybook for Django 2 | 3 | [![npm](https://img.shields.io/npm/v/storybook-django.svg)](https://www.npmjs.com/package/storybook-django) [![Build status](https://github.com/torchbox/storybook-django/workflows/CI/badge.svg)](https://github.com/torchbox/storybook-django/actions) 4 | 5 | Storybook for Django is an experimental UI development environment for Django components. It allows you to implement, document, and test UI components in isolation from your Django views. 6 | 7 | ![Screenshot of the Storybook UI, with a Django UI component displaying](.github/storybook-django-screenshot.png) 8 | 9 | ## How it works 10 | 11 | Server-side, this uses [django-pattern-library](https://github.com/torchbox/django-pattern-library) to mock template context and template tags. Client-side, we use [Storybook](https://storybook.js.org/) to create stories from our templates. 12 | 13 | ## Getting started 14 | 15 | Let’s get you set up. There are two requirements: 16 | 17 | 1. First, start by setting up [django-pattern-library](https://github.com/torchbox/django-pattern-library), v0.7.0 and up. Have a look at our [demo settings.py](https://github.com/torchbox/storybook-django/blob/main/demo/settings.py) as an example. 18 | 2. Then, set up [Storybook](https://storybook.js.org/). We expect `storybook-django` to work with any framework supported by Storybook, and provide built-in support for React (Vue in progress). 19 | 20 | Next, install our package: 21 | 22 | ```sh 23 | npm install --save-dev storybook-django 24 | ``` 25 | 26 | ### Middleware 27 | 28 | Add a `middleware.js` inside your Storybook configuration folder (`.storybook` by default): 29 | 30 | ```js 31 | const { 32 | createDjangoAPIMiddleware, 33 | } = require('storybook-django/src/middleware'); 34 | 35 | module.exports = createDjangoAPIMiddleware({ 36 | // Point this at your Django runserver instance, with the correct port number. 37 | origin: 'http://localhost:8001', 38 | apiPath: ['/pattern-library/'], 39 | }); 40 | ``` 41 | 42 | This will forward pattern library API requests to Django. You may optionally add more API path patterns to `apiPath` to make other requests to your Django backend. 43 | 44 | ### Optional Webpack configuration 45 | 46 | This is optional but highly recommended. To leverage Storybook’s live-reloading and documentation capabilities, we need to configure it to load our templates. Edit your Storybook `main.js` file to customise the `webpackFinal` option: 47 | 48 | ```js 49 | module.exports = { 50 | webpackFinal: (config) => { 51 | config.module.rules = config.module.rules.concat([ 52 | { 53 | test: /\.html$/, 54 | // Webpack 5: 55 | type: 'asset/source', 56 | // Webpack 4 (make sure to also install the raw-loader package): 57 | // use: 'raw-loader', 58 | }, 59 | ]); 60 | 61 | return config; 62 | } 63 | ``` 64 | 65 | ### React usage 66 | 67 | Here is the most basic story for a Django template: 68 | 69 | ```js 70 | import { Pattern } from 'storybook-django/src/react'; 71 | 72 | export default {}; 73 | 74 | export const Base = () => ( 75 | 79 | ); 80 | ``` 81 | 82 | `Pattern` uses a hard-coded `endpoint` of `/pattern-library/api/v1/render-pattern` by default. To change this, pass a different value. For example, 83 | 84 | ```js 85 | 86 | ``` 87 | 88 | If this is a necessity for your project, consider creating your own wrapper for the `Pattern` component rather than having to define the `endpoint` in all stories. 89 | 90 | #### With auto-generated template paths 91 | 92 | Our `Pattern` component has to be told which `template` to render, Alternatively, we can use [Webpack’s `__filename`](https://webpack.js.org/api/module-variables/#__filename-nodejs) support to auto-generate the template path. First, configure Webpack: 93 | 94 | ```js 95 | config.node = { 96 | __filename: true, 97 | }; 98 | ``` 99 | 100 | Then, use the `filename` prop instead of `template`: 101 | 102 | ```js 103 | 104 | ``` 105 | 106 | This `filename` prop assumes the template is in the same folder as the template, with the same file name except for the extension (replaces `.stories.(js|tsx)` with `.html`). 107 | 108 | #### With Storybook features 109 | 110 | And here is a more advanced examples, showcasing different Storybook features: 111 | 112 | - Setting a custom title for the story. 113 | - Loading Markdown files to use as documentation. 114 | - Loading the component’s template to display alongside the docs, and for live-reloading. 115 | - Setting up [controls](https://storybook.js.org/docs/react/essentials/controls). 116 | - Having multiple stories with different data. 117 | 118 | ```js 119 | import { Pattern } from 'storybook-django/src/react'; 120 | 121 | import docs from './quote_block.md'; 122 | import template from './quote_block.html'; 123 | 124 | export default { 125 | title: 'Components / quote_block', 126 | parameters: { 127 | docs: { 128 | source: { code: template }, 129 | extractComponentDescription: () => docs, 130 | }, 131 | }, 132 | argTypes: { 133 | quote: { 134 | control: { type: 'text' }, 135 | description: 'Will be displayed first', 136 | }, 137 | attribution: { 138 | control: { type: 'text' }, 139 | description: 'Underneath the quote (optional)', 140 | }, 141 | }, 142 | }; 143 | 144 | export const Base = (args) => ( 145 | 146 | ); 147 | 148 | Base.args = { 149 | quote: 'Someone believed in me once and now it’s time for me to do the same.', 150 | attribution: 'Young person', 151 | }; 152 | 153 | export const NoAttribution = Base.bind({}); 154 | 155 | NoAttribution.args = { 156 | quote: Base.args.quote, 157 | attribution: null, 158 | }; 159 | ``` 160 | 161 | #### Making the most of React 162 | 163 | The point of using React is to be able to fully customise the context within which our Django components are displayed. Here is an example, with a simple SVG icon template: 164 | 165 | ```js 166 | const IconPattern = (props) => ( 167 | 172 | ); 173 | 174 | export const ReactDemoStory = () => ( 175 | 176 | 177 | View our complete guide 178 | 179 | 180 | ); 181 | ``` 182 | 183 | ### Vue usage 184 | 185 | We are working on Vue support. Please refer to [Usage with Vue #7](https://github.com/torchbox/storybook-django/issues/7) in the meantime, and provide feedback. 186 | 187 | ### Usage with other frameworks 188 | 189 | storybook-django’s implementation is largely framework-agnostic, and should work equally as well with Storybook’s HTML and Web Components support. 190 | 191 | You will need to directly import the imperative APIs: 192 | 193 | ```js 194 | import { 195 | renderPattern, 196 | simulateLoading, 197 | insertHTMLWithScripts, 198 | } from 'storybook-django'; 199 | ``` 200 | 201 | - `renderPattern` calls the django-pattern-library API rendering endpoint. 202 | - `simulateLoading` includes `insertHTMLWithScripts`, and fires a `DOMContentLoaded` event. 203 | - `insertHTMLWithScripts` is like `.innerHTML`, but additionally executing any ` 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /demo/core/templates/patterns/components/InfoBox/InfoBox.scss: -------------------------------------------------------------------------------- 1 | $spacing--mobile: 20px; 2 | $spacing--desktop: 40px; 3 | 4 | .info-box { 5 | $root: &; 6 | position: relative; 7 | padding: $spacing--mobile; 8 | padding-top: 30px; 9 | border: 1px solid transparent; 10 | border-radius: 5px; 11 | 12 | @include media-query(tablet-portrait) { 13 | padding: $spacing--desktop 30px 30px; 14 | } 15 | 16 | &--default { 17 | background: $color--white; 18 | border-color: $color--blue; 19 | } 20 | 21 | &--checkmark { 22 | background: transparent; 23 | border-color: $color--pink; 24 | 25 | #{$root}__icon { 26 | background: $color--pink; 27 | color: $color--white; 28 | width: 46px; 29 | height: 46px; 30 | padding: 8px; 31 | left: 0; 32 | right: 0; 33 | margin: 0 auto; 34 | 35 | @include media-query(tablet-portrait) { 36 | border-color: $color--light-blue; 37 | left: initial; 38 | right: initial; 39 | } 40 | } 41 | } 42 | 43 | &__icon { 44 | position: absolute; 45 | top: 0; 46 | transform: translateY(-50%); 47 | color: $color--white; 48 | border-radius: 100%; 49 | border: 3px solid currentColor; 50 | } 51 | 52 | p { 53 | color: $color--dark-grey; 54 | } 55 | 56 | ul { 57 | @include bullet-list(); 58 | } 59 | 60 | a { 61 | @include link-arrow-hover(right); 62 | color: $color--pink; 63 | font-weight: $weight--semibold; 64 | 65 | &:hover { 66 | color: $color--purple; 67 | } 68 | } 69 | 70 | &__cta { 71 | a { 72 | font-weight: $weight--semibold; 73 | text-decoration: none; 74 | 75 | .icon { 76 | width: 10px; 77 | height: 12px; 78 | margin-left: 6px; 79 | stroke: currentColor; 80 | vertical-align: middle; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /demo/core/templates/patterns/components/InfoBox/InfoBox.stories.js: -------------------------------------------------------------------------------- 1 | import InfoBox from './InfoBox'; 2 | 3 | const wrapperStyles = { 4 | margin: '23px', 5 | }; 6 | 7 | export default { 8 | title: 'InfoBox', 9 | component: InfoBox, 10 | decorators: [ 11 | (Story) => ( 12 |
13 | 14 |
15 | ), 16 | ], 17 | }; 18 | 19 | export const Base = (args) => ( 20 | 21 |

This is a title

22 |
    23 |
  • List item 1
  • 24 |
  • List item 2
  • 25 |
  • List item 3
  • 26 |
27 |
28 | ); 29 | Base.args = { ctaLabel: 'CTA Text learn more', ctaHref: '/faq' }; 30 | 31 | export const CheckmarkTheme = Base.bind(null); 32 | CheckmarkTheme.args = { ...Base.args, theme: 'checkmark' }; 33 | 34 | export const Headings = () => ( 35 | 36 |

This is a title

37 |

This is a title

38 |

This is a title

39 |

40 | This is a paragraph Link text and stuff and text and 41 | things. This is a paragraph tag text and stuff and text and things. 42 |

43 |
44 | ); 45 | -------------------------------------------------------------------------------- /demo/core/templates/patterns/components/InfoBox/InfoBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Icon, { IconName } from '../icon/Icon'; 4 | import Button from '../button/Button'; 5 | 6 | const THEMES = { 7 | default: 'info-box info-box--default', 8 | checkmark: 'info-box info-box--checkmark', 9 | } as const; 10 | type Theme = keyof typeof THEMES; 11 | 12 | const THEME_ICONS: { [key in Theme]: IconName } = { 13 | default: 'information', 14 | checkmark: 'checkmark', 15 | }; 16 | 17 | export const INFOBOX_THEMES = Object.keys(THEMES); 18 | 19 | interface InfoBoxProps { 20 | children: React.ReactNode; 21 | ctaLabel?: string; 22 | ctaTo?: string; 23 | ctaHref?: string; 24 | theme?: Theme; 25 | className?: string; 26 | } 27 | 28 | /** 29 | * A wrapper for arbitrary rich text content, usually displayed in a sidebar. 30 | * Can optionally have a call to action link at the bottom. 31 | */ 32 | const InfoBox = ({ 33 | children, 34 | ctaLabel, 35 | ctaTo, 36 | ctaHref, 37 | theme = 'default', 38 | className, 39 | }: InfoBoxProps) => { 40 | const infoBoxClassName = `${THEMES[theme]} ${className || ''}`; 41 | const iconName = THEME_ICONS[theme]; 42 | 43 | return ( 44 | 61 | ); 62 | }; 63 | 64 | export default InfoBox; 65 | -------------------------------------------------------------------------------- /demo/core/templates/patterns/components/LoadingIndicator/LoadingIndicator.scss: -------------------------------------------------------------------------------- 1 | @keyframes spinner-animation { 2 | 0% { 3 | opacity: 1; 4 | } 5 | 100% { 6 | opacity: 0; 7 | } 8 | } 9 | 10 | .loading-indicator { 11 | $root: &; 12 | padding: 10px 15px; 13 | 14 | @include media-query(tablet-portrait) { 15 | padding: 0; 16 | } 17 | 18 | &__heading { 19 | @include font-size(xl); 20 | color: $color--blue; 21 | } 22 | 23 | &__message { 24 | @include font-size(m); 25 | color: $color--dark-grey; 26 | } 27 | 28 | // Generated from https://loading.io 29 | &__animation { 30 | position: relative; 31 | width: 96px; 32 | height: 96px; 33 | transform: translate(-48px, -48px) scale(0.48) translate(48px, 48px); 34 | 35 | > div { 36 | position: absolute; 37 | left: 88px; 38 | top: 28px; 39 | animation: spinner-animation linear 1s infinite; 40 | background: $color--pink; 41 | width: 24px; 42 | height: 24px; 43 | border-radius: 50%; 44 | transform-origin: 12px 72px; 45 | 46 | &:nth-child(1) { 47 | transform: rotate(0deg); 48 | animation-delay: -0.875s; 49 | } 50 | 51 | &:nth-child(2) { 52 | transform: rotate(45deg); 53 | animation-delay: -0.75s; 54 | } 55 | 56 | &:nth-child(3) { 57 | transform: rotate(90deg); 58 | animation-delay: -0.625s; 59 | } 60 | 61 | &:nth-child(4) { 62 | transform: rotate(135deg); 63 | animation-delay: -0.5s; 64 | } 65 | 66 | &:nth-child(5) { 67 | transform: rotate(180deg); 68 | animation-delay: -0.375s; 69 | } 70 | 71 | &:nth-child(6) { 72 | transform: rotate(225deg); 73 | animation-delay: -0.25s; 74 | } 75 | 76 | &:nth-child(7) { 77 | transform: rotate(270deg); 78 | animation-delay: -0.125s; 79 | } 80 | 81 | &:nth-child(8) { 82 | transform: rotate(315deg); 83 | animation-delay: 0s; 84 | } 85 | } 86 | } 87 | 88 | &--centre { 89 | text-align: center; 90 | 91 | #{$root}__animation { 92 | margin: 0 auto; 93 | } 94 | } 95 | 96 | // Re-order elements for LTR layout 97 | &--left { 98 | text-align: left; 99 | display: flex; 100 | 101 | #{$root}__animation { 102 | order: 1; 103 | } 104 | 105 | #{$root}__content { 106 | order: 2; 107 | padding: 6px 0 0 20px; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /demo/core/templates/patterns/components/LoadingIndicator/LoadingIndicator.stories.js: -------------------------------------------------------------------------------- 1 | import LoadingIndicator from './LoadingIndicator'; 2 | 3 | export default { 4 | title: 'LoadingIndicator', 5 | component: LoadingIndicator, 6 | }; 7 | 8 | export const Base = (args) => ( 9 | Loading… 10 | ); 11 | -------------------------------------------------------------------------------- /demo/core/templates/patterns/components/LoadingIndicator/LoadingIndicator.tsx: -------------------------------------------------------------------------------- 1 | const THEMES = { 2 | centre: 'loading-indicator loading-indicator--centre', 3 | left: 'loading-indicator loading-indicator--left', 4 | } as const; 5 | type Theme = keyof typeof THEMES; 6 | export const INDICATOR_THEMES = Object.keys(THEMES); 7 | 8 | interface LoadingIndicatorProps { 9 | children: JSX.Element; 10 | theme?: Theme; 11 | className?: string; 12 | } 13 | 14 | /** 15 | * This component uses markup generated from https://loading.io 16 | */ 17 | const LoadingIndicator = ({ 18 | children, 19 | theme = 'centre', 20 | className, 21 | }: LoadingIndicatorProps) => { 22 | const indicatorClassName = `${THEMES[theme]} ${className || ''}`; 23 | 24 | return ( 25 |
26 |