├── .gitattributes ├── .gitignore ├── DEVELOPER_GUIDE.md ├── MANIFEST.in ├── README.md ├── apps ├── app.py ├── hello_world.py └── notebook.ipynb ├── assets ├── images │ └── panel-modal.jpg └── videos │ └── panel-modal-intro.gif ├── code-of-conduct.md ├── pyproject.toml ├── src └── panel_modal │ ├── __init__.py │ ├── modal.py │ └── py.typed └── tests ├── __init__.py └── test_modal.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Line Endings configuration file for Git 2 | # See https://docs.github.com/en/free-pro-team@latest/github/using-git/configuring-git-to-handle-line-endings 3 | # Set the default behavior, in case people don't have or can't have core.autocrlf set. 4 | * text=auto 5 | 6 | # Explicitly declare text files you want to always be normalized and converted 7 | # to native line endings on checkout. 8 | *.py text 9 | *.js text 10 | *.html text 11 | *.css text 12 | *.yml text 13 | *.md text 14 | 15 | # Denote all files that are truly binary and should not be modified. 16 | *.png binary 17 | *.jpg binary 18 | *.gif binary 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | .idea/ 161 | .vscode/ 162 | pip-wheel-metadata/ 163 | build/ 164 | test_results/ 165 | script.py 166 | -------------------------------------------------------------------------------- /DEVELOPER_GUIDE.md: -------------------------------------------------------------------------------- 1 | # ❤️ Developer Guide 2 | 3 | Welcome. We are so happy that you want to contribute. 4 | 5 | Please note that this project is released with a [Contributor Code of Conduct](code-of-conduct.md). 6 | By participating in this project you agree to abide by its terms. 7 | 8 | ## 🧳 Prerequisites 9 | 10 | - A working [Python](https://www.python.org/downloads/) environment. 11 | - [Git CLI](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). 12 | 13 | ## 📙 How to 14 | 15 | Below we describe how to install and use this project for development. 16 | 17 | ### 💻 Install for Development 18 | 19 | To install for development you will need to create a new environment 20 | 21 | Then run 22 | 23 | ```bash 24 | git clone https://github.com/awesome-panel/panel-modal.git 25 | cd panel-modal 26 | pip install pip -U 27 | pip install -e .[dev,examples] 28 | ``` 29 | 30 | Then you can see the available commands via 31 | 32 | ```bash 33 | pn --help 34 | ``` 35 | 36 | You can run all tests via 37 | 38 | ```bash 39 | pn test all 40 | ``` 41 | 42 | Please run this command and fix any failing tests if possible before you `git push`. 43 | 44 | ### 🚢 Release a new package on Pypi 45 | 46 | Update the version in the [__init__.py](src/panel_modal/__init__.py). 47 | 48 | Then run 49 | 50 | ```bash 51 | pn test all 52 | ``` 53 | 54 | Then you can build 55 | 56 | ```bash 57 | pn build package 58 | ``` 59 | 60 | and upload 61 | 62 | ```bash 63 | pn release package 64 | ``` 65 | 66 | to release the package 📦. To upload to *Test Pypi* first, you can add the `--test` flag. 67 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include src/panel_modal/py.typed -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ✨ panel-modal 2 | 3 | We want to make it easy to use *modals* with Panel on both the server and in the notebook. 4 | 5 | A *modal* is an element that displays in front of and deactivates all other page content. Panel 6 | already includes a modal. But it only works if you using a *template* on a server. It does not 7 | work in the notebook. 8 | 9 | You can install and use the package as simple as. 10 | 11 | ```bash 12 | pip install panel-modal 13 | ``` 14 | 15 | ```python 16 | import panel as pn 17 | 18 | from panel_modal import Modal 19 | 20 | pn.extension("modal") 21 | 22 | modal = Modal(pn.panel("Hi. I am the Panel Modal!", width=200)) 23 | 24 | pn.Column(modal.param.open, modal).servable() 25 | ``` 26 | 27 | ![Panel Modal in Notebook](assets/images/panel-modal.jpg) 28 | 29 | Check out the [api](#api) section below and the [examples](apps) folder for more details. 30 | 31 | ![Project Intro](assets/videos/panel-modal-intro.gif) 32 | 33 | ## Api 34 | 35 | ### Parameters 36 | 37 | - `objects` : The objects to display in the modal. You can define the size of the modal if you 38 | wrap the objects in a layout like a Column. 39 | - `is_open`: Whether or not the modal is open. Set this to `True` to open the modal. 40 | - `show_close_button`: Whether to show a close button in the modal. 41 | - `style`: The css styles applied to the modal. 42 | 43 | ### Events 44 | 45 | - `open`: Trigger this to open the modal. 46 | - `close`: Trigger this to close the modal. 47 | 48 | ## 🚀 Get started in under a minute 49 | 50 | Install `panel-modal` including the *`examples` dependencies*. 51 | 52 | ```bash 53 | pip install panel-modal[examples] 54 | ``` 55 | 56 | Explore the sample apps 57 | 58 | ```bash 59 | pn hello panel-modal 60 | ``` 61 | 62 | You can now find the *reference* and *gallery* notebooks in the `examples/awesome-panel/panel-modal` folder. Check them out by running `jupyter lab`. 63 | 64 | ## 📒 Explore the examples online 65 | 66 | Click one of the buttons 67 | 68 | [![nbviewer](https://raw.githubusercontent.com/jupyter/design/master/logos/Badges/nbviewer_badge.svg)](https://nbviewer.org/github/awesome-panel/panel-modal/tree/main/examples/) 69 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/awesome-panel/panel-modal/HEAD) 70 | 71 | ## ⭐ Support 72 | 73 | Please support [Panel](https://panel.holoviz.org) and 74 | [awesome-panel](https://awesome-panel.org) by giving the projects a star on Github: 75 | 76 | - [holoviz/panel](https://github.com/holoviz/panel). 77 | - [awesome-panel/awesome-panel](https://github.com/awesome-panel/awesome-panel). 78 | 79 | Thanks 80 | 81 | ## ❤️ Contribute 82 | 83 | If you are looking to contribute to this project you can find ideas in the [issue tracker](https://github.com/awesome-panel/panel-modal/issues). To get started check out the [DEVELOPER_GUIDE](DEVELOPER_GUIDE.md). 84 | 85 | I would love to support and receive your contributions. Thanks. 86 | 87 | [![Hacktober Fest](https://github.blog/wp-content/uploads/2022/10/hacktoberfestbanner.jpeg?fit=1200%2C630)](https://github.com/awesome-panel/panel-modal/issues). 88 | 89 | ## Monitor 90 | 91 | [![PyPI version](https://badge.fury.io/py/panel-modal.svg)](https://pypi.org/project/panel-modal/) 92 | [![Downloads](https://pepy.tech/badge/panel-modal/month)](https://pepy.tech/project/panel-modal) 93 | ![Python Versions](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10-blue) 94 | [![License](https://img.shields.io/badge/License-MIT%202.0-blue.svg)](https://opensource.org/licenses/MIT) 95 | ![Test Results](https://github.com/awesome-panel/panel-modal/actions/workflows/tests.yaml/badge.svg?branch=main) 96 | -------------------------------------------------------------------------------- /apps/app.py: -------------------------------------------------------------------------------- 1 | """# The Panel `Modal` works on both the server and in the notebook 2 | 3 | ```bash 4 | pip install panel-modal 5 | ``` 6 | 7 | ```python 8 | import panel as pn 9 | 10 | from panel_modal import Modal 11 | 12 | pn.extension() 13 | 14 | modal = Modal(pn.panel("Hi. I am the Panel Modal!", width=200)) 15 | 16 | pn.Column(modal.param.open, modal).servable() 17 | ``` 18 | """ 19 | import panel as pn 20 | import hvplot.pandas # noqa 21 | import pandas as pd 22 | 23 | from panel_modal import Modal 24 | 25 | pn.extension("modal", sizing_mode="stretch_width") 26 | 27 | age_list = [8, 10, 12, 14, 72, 74, 76, 78, 20, 25, 30, 35, 60, 85] 28 | df = pd.DataFrame({"gender": list("MMMMMMMMFFFFFF"), "age": age_list}) 29 | plot = df.hvplot.box(y='age', by='gender', height=400, width=400, legend=False, ylim=(0, None)) 30 | 31 | content = pn.Column( 32 | "## Hi. I'm a *modal*", pn.panel(plot), sizing_mode="fixed", width=600 33 | ) 34 | modal = Modal(content) 35 | 36 | layout = pn.Column(modal.param.open, modal, modal.param.is_open, modal.param.show_close_button) 37 | 38 | pn.template.FastListTemplate( 39 | site="Awesome Panel", site_url="./", 40 | title="Panel Modal", 41 | favicon="https://raw.githubusercontent.com/MarcSkovMadsen/awesome-panel-assets/320297ccb92773da099f6b97d267cc0433b67c23/favicon/ap-1f77b4.ico", 42 | main=[__doc__, layout] 43 | ).servable() -------------------------------------------------------------------------------- /apps/hello_world.py: -------------------------------------------------------------------------------- 1 | import panel as pn 2 | 3 | from panel_modal import Modal 4 | 5 | pn.extension("modal") 6 | 7 | modal = Modal(pn.panel("Hi. I am the Panel Modal!", width=200)) 8 | 9 | pn.Column(modal.param.open, modal).servable() 10 | 11 | -------------------------------------------------------------------------------- /apps/notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "aaac1313-e203-4d79-82d1-a316671a2022", 6 | "metadata": {}, 7 | "source": [ 8 | "# Panel Modal\n", 9 | "\n", 10 | "A *modal* is an element that displays in front of and deactivates all other page content. Panel\n", 11 | "already includes a modal. But it only works if you using a *template* on a server. It does not\n", 12 | "work in the notebook.\n", 13 | "\n", 14 | "You can install and use the package as simple as.\n", 15 | "\n", 16 | "```bash\n", 17 | "pip install panel-modal\n", 18 | "```" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "id": "e57edfe6-3fbe-484e-963d-d93efef0aa5f", 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import panel as pn\n", 29 | "import hvplot.pandas # noqa\n", 30 | "import pandas as pd\n", 31 | "\n", 32 | "from panel_modal import Modal\n", 33 | "\n", 34 | "pn.extension(\"modal\", sizing_mode=\"stretch_width\")" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "72e383f8-b7fb-4beb-ba5e-08b0b7dcb915", 40 | "metadata": {}, 41 | "source": [ 42 | "Lets create some `content` to display a the `Modal`" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "30043f44-4b4f-4090-b329-86007a342711", 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "age_list = [8, 10, 12, 14, 72, 74, 76, 78, 20, 25, 30, 35, 60, 85]\n", 53 | "df = pd.DataFrame({\"gender\": list(\"MMMMMMMMFFFFFF\"), \"age\": age_list})\n", 54 | "plot = df.hvplot.box(y='age', by='gender', height=400, width=400, legend=False, ylim=(0, None))\n", 55 | "\n", 56 | "content = pn.Column(\n", 57 | " \"## Hi. I'm a *modal*\", plot, \"allo\", sizing_mode=\"fixed\", width=600\n", 58 | ")" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "id": "9374fa8e-925b-456c-982a-3d872f8d8ba9", 64 | "metadata": {}, 65 | "source": [ 66 | "Lets create the `modal`" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "id": "e15915df-cb6d-47d0-8513-463df5a847cc", 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "modal = Modal(content)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "id": "f6a18d7e-c75a-4ddc-a6f4-4ebdb018bdfc", 82 | "metadata": {}, 83 | "source": [ 84 | "Let us create a `Column` *layout* containing and `open` button and the `modal`." 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "id": "426d2a11-71d6-4fbd-b0c9-737c5ee4614e", 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "pn.Column(modal.param.open, modal, modal.param.is_open, modal.param.show_close_button).servable()" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "id": "afe4fdeb-6de6-4961-82ce-e7cf6e9671a5", 100 | "metadata": {}, 101 | "source": [ 102 | "Try clicking the *Open* button." 103 | ] 104 | } 105 | ], 106 | "metadata": { 107 | "kernelspec": { 108 | "display_name": "Python [conda env:root] *", 109 | "language": "python", 110 | "name": "conda-root-py" 111 | }, 112 | "language_info": { 113 | "codemirror_mode": { 114 | "name": "ipython", 115 | "version": 3 116 | }, 117 | "file_extension": ".py", 118 | "mimetype": "text/x-python", 119 | "name": "python", 120 | "nbconvert_exporter": "python", 121 | "pygments_lexer": "ipython3", 122 | "version": "3.11.6" 123 | } 124 | }, 125 | "nbformat": 4, 126 | "nbformat_minor": 5 127 | } 128 | -------------------------------------------------------------------------------- /assets/images/panel-modal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-panel/panel-modal/5c2cc6fa293880fffb788591c09a62017c8427ba/assets/images/panel-modal.jpg -------------------------------------------------------------------------------- /assets/videos/panel-modal-intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-panel/panel-modal/5c2cc6fa293880fffb788591c09a62017c8427ba/assets/videos/panel-modal-intro.gif -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at florian.kromer@mailbox.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools",] 3 | build-backend = "setuptools.build_meta" 4 | [project] 5 | name = "panel-modal" 6 | description = "This package makes it super simple to do exploratory data analysis and develop high-quality Panel data apps ..." 7 | readme = "README.md" 8 | requires-python = ">=3.7" 9 | keywords = ["python", "holoviz", "panel", "dataviz", "dataapp", "dashboard", "datascience", "analytics"] 10 | license = {file = "LICENSE"} 11 | classifiers = [ 12 | "License :: OSI Approved :: MIT License", 13 | "Development Status :: 3 - Alpha", 14 | "Programming Language :: Python :: 3", 15 | "Programming Language :: Python :: 3.7", 16 | "Programming Language :: Python :: 3.8", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Operating System :: OS Independent", 20 | "Intended Audience :: Developers", 21 | "Intended Audience :: Education", 22 | "Intended Audience :: Financial and Insurance Industry", 23 | "Intended Audience :: Healthcare Industry", 24 | "Intended Audience :: Information Technology", 25 | "Intended Audience :: Legal Industry", 26 | "Intended Audience :: Other Audience", 27 | "Intended Audience :: Science/Research", 28 | "Natural Language :: English", 29 | "Topic :: Office/Business :: Financial", 30 | "Topic :: Office/Business", 31 | "Topic :: Scientific/Engineering :: Information Analysis", 32 | "Topic :: Scientific/Engineering :: Visualization", 33 | "Topic :: Scientific/Engineering", 34 | "Topic :: Software Development :: Libraries", 35 | ] 36 | dependencies = [ 37 | "bokeh", 38 | "panel", 39 | "param", 40 | ] 41 | dynamic = ["version"] 42 | 43 | authors = [ 44 | {name = "awesome-panel"} 45 | ] 46 | 47 | [project.optional-dependencies] 48 | dev = [ 49 | "awesome-panel-cli[dev]" 50 | ] 51 | examples = [ 52 | "awesome-panel-cli", 53 | "hvplot", 54 | "notebook", 55 | "pandas", 56 | ] 57 | 58 | [project.urls] 59 | repository = "https://github.com/awesome-panel/panel-modal" 60 | 61 | [tool.setuptools.dynamic] 62 | version = {attr = "panel_modal.VERSION"} 63 | 64 | [tool.black] 65 | line-length = 100 66 | 67 | [tool.isort] 68 | profile = "black" 69 | src_paths = ["src", "tests"] 70 | 71 | [tool.pylint.main] 72 | py-version=3.9 73 | output-format = "colorized" 74 | max-attributes=12 75 | max-args=10 76 | 77 | [tool.pylint.format] 78 | max-module-lines = 1000 79 | 80 | [tool.pylint.'MESSAGES CONTROL'] 81 | max-line-length = 100 82 | disable = [] 83 | 84 | [tool.mypy] 85 | python_version = "3.9" 86 | namespace_packages = true 87 | explicit_package_bases = true 88 | mypy_path = "src" 89 | exclude = [] 90 | 91 | [[tool.mypy.overrides]] 92 | module = [ 93 | "bokeh.*", 94 | "holoviews.*", 95 | "hvplot.*", 96 | "param.*", 97 | "pyviz_comms.*", 98 | "IPython.*", 99 | ] 100 | ignore_missing_imports = true 101 | 102 | # https://github.com/pytest-dev/pytest/blob/main/pyproject.toml 103 | [tool.pytest.ini_options] 104 | minversion = "7.0" 105 | addopts = "-s" 106 | testpaths = ["tests"] 107 | junit_family = "legacy" 108 | python_files = ["tests.py","test_*.py"] 109 | markers = [ 110 | "unittest", # Small, isolated test 111 | "integrationtest", # A test towards some external system or service. For example a database 112 | "functionaltest", # Large potentially non-isolated test requiring access to external services 113 | "slow", # A slow test. Skip normally. But run this when you have the time. 114 | "skip_in_build_server", # Tests that should be skipped on the build server 115 | ] 116 | filterwarnings=[ 117 | "ignore:the imp module is deprecated in favour of importlib.*:DeprecationWarning", 118 | "ignore:inspect.getargspec.*:DeprecationWarning" 119 | ] 120 | 121 | [tool.coverage.run] 122 | omit=[] 123 | 124 | [html] 125 | directory="test_results/cov_html" 126 | skip_empty=true 127 | 128 | [tool.bandit] 129 | exclude_dirs = [".venv", "venv",] 130 | severity="high" 131 | 132 | [tool.bandit.assert_used] 133 | exclude = ["*_test.py", "test_*.py"] -------------------------------------------------------------------------------- /src/panel_modal/__init__.py: -------------------------------------------------------------------------------- 1 | """This package provides a Panel Modal that works both on the server and in the notebook. 2 | 3 | ..code-block:: 4 | 5 | import panel as pn 6 | from panel_modal import Modal 7 | 8 | pn.extension() 9 | 10 | modal = Modal(pn.panel("Hi. I am the Panel Modal!", width=200)) 11 | 12 | pn.Column(modal.param.open, modal).servable() 13 | """ 14 | from .modal import Modal 15 | 16 | VERSION = "0.4.0" 17 | 18 | __all__ = ["Modal", "VERSION"] 19 | -------------------------------------------------------------------------------- /src/panel_modal/modal.py: -------------------------------------------------------------------------------- 1 | """A *modal* is an element that displays in front of and deactivates all other page content.""" 2 | import param 3 | from panel.layout.base import NamedListLike 4 | from panel.reactive import ReactiveHTML 5 | 6 | JS_FILE = "https://cdn.jsdelivr.net/npm/a11y-dialog@7/dist/a11y-dialog.min.js" 7 | 8 | STYLE = """ 9 | .dialog-container, 10 | .dialog-overlay { 11 | position: fixed; 12 | top: 0; 13 | right: 0; 14 | bottom: 0; 15 | left: 0; 16 | } 17 | .dialog-container { 18 | z-index: 100002; 19 | display: flex; 20 | } 21 | .dialog-container[aria-hidden='true'] { 22 | display: none; 23 | } 24 | .dialog-overlay { 25 | z-index: 100001; 26 | background-color: rgb(43 46 56 / 0.9); 27 | } 28 | .dialog-content { 29 | margin: auto; 30 | z-index: 100002; 31 | position: relative; 32 | background-color: white; 33 | border-radius: 2px; 34 | padding: 10px; 35 | padding-bottom: 20px; 36 | } 37 | fast-design-system-provider .dialog-content { 38 | background-color: var(--background-color); 39 | border-radius: calc(var(--corner-radius) * 1px); 40 | } 41 | @keyframes fade-in { 42 | from { 43 | opacity: 0; 44 | } 45 | } 46 | @keyframes slide-up { 47 | from { 48 | transform: translateY(10%); 49 | } 50 | } 51 | .dialog-overlay { 52 | animation: fade-in 200ms both; 53 | } 54 | .dialog-content { 55 | animation: fade-in 400ms 200ms both, slide-up 400ms 200ms both; 56 | } 57 | @media (prefers-reduced-motion: reduce) { 58 | .dialog-overlay, 59 | .dialog-content { 60 | animation: none; 61 | } 62 | } 63 | .pnx-dialog-close { 64 | position: absolute; 65 | top: 0.5em; 66 | right: 0.5em; 67 | border: 0; 68 | padding: 0.25em; 69 | background-color: transparent; 70 | font-size: 1.5em; 71 | width: 1.5em; 72 | height: 1.5em; 73 | text-align: center; 74 | cursor: pointer; 75 | transition: 0.15s; 76 | border-radius: 50%; 77 | z-index: 10003; 78 | } 79 | fast-design-system-provider .pnx-dialog-close { 80 | color: var(--neutral-foreground-rest); 81 | } 82 | .pnx-dialog-close:hover { 83 | background-color: rgb(50 50 0 / 0.15);; 84 | } 85 | fast-design-system-provider .pnx-dialog-close:hover { 86 | background-color: var(--neutral-fill-hover); 87 | } 88 | .lm-Widget.p-Widget.lm-TabBar.p-TabBar.lm-DockPanel-tabBar.jp-Activity { 89 | z-index: -1; 90 | } 91 | """ 92 | 93 | JS = """ 94 | 97 | """ 98 | 99 | 100 | class Modal(ReactiveHTML, NamedListLike): # pylint: disable=too-many-ancestors 101 | """A *modal* is an element that displays in front of and deactivates all other page content. 102 | 103 | You will need to include the Modal in your layout or template. It will not be shown before 104 | you `open` it. 105 | 106 | Args: 107 | object: The object to display in the modal. You can display multiple objects by 108 | wrapping them in a layout like a Column. 109 | 110 | Example: 111 | 112 | ..code-block: 113 | 114 | import panel as pn 115 | from panel_modal import Modal 116 | 117 | pn.extension() 118 | 119 | modal = Modal(pn.panel("Hi. I am the Panel Modal!", width=200)) 120 | 121 | pn.Column(modal.param.open, modal).servable() 122 | """ 123 | 124 | open = param.Event(doc="Trigger this to open the modal.") 125 | close = param.Event(doc="Trigger this to close the modal.") 126 | is_open = param.Boolean(doc="Whether or not the modal is open.") 127 | show_close_button = param.Boolean(True, doc="Whether to show a close button in the modal") 128 | 129 | style = param.String(STYLE, doc="The css styles applied to the modal") 130 | 131 | def __init__(self, *objects, **params): # pylint: disable=redefined-builtin 132 | params["height"] = params["width"] = params["margin"] = 0 133 | NamedListLike.__init__(self, *objects, **params) 134 | ReactiveHTML.__init__(self, objects=self.objects, **params) 135 | 136 | @param.depends("open", watch=True) 137 | def _show(self): 138 | self.is_open = True 139 | 140 | @param.depends("close", watch=True) 141 | def _hide(self): 142 | self.is_open = False 143 | 144 | _extension_name = "modal" 145 | 146 | __javascript__ = [JS_FILE] 147 | 148 | _template = """ 149 | 152 | 168 | """ 169 | 170 | _scripts = { 171 | "render": """ 172 | fast_el = document.getElementById("body-design-provider") 173 | if (fast_el!==null){ 174 | fast_el.appendChild(pnx_dialog_style) 175 | fast_el.appendChild(pnx_dialog) 176 | } 177 | self.show_close_button() 178 | self.init_modal() 179 | """, 180 | "init_modal": """ 181 | state.modal = new A11yDialog(pnx_dialog) 182 | state.modal.on('show', function (element, event) {data.is_open=true}) 183 | state.modal.on('hide', function (element, event) {data.is_open=false}) 184 | if (data.is_open==true){state.modal.show()} 185 | """, 186 | "is_open": """\ 187 | if (data.is_open==true){state.modal.show();view.invalidate_layout()} else {state.modal.hide()}""", 188 | "show_close_button": """ 189 | if (data.show_close_button){pnx_dialog_close.style.display = " block"}else{pnx_dialog_close.style.display = "none"} 190 | """, 191 | } 192 | -------------------------------------------------------------------------------- /src/panel_modal/py.typed: -------------------------------------------------------------------------------- 1 | # Marker file for PEP 561. The mypy package uses inline types. -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-panel/panel-modal/5c2cc6fa293880fffb788591c09a62017c8427ba/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_modal.py: -------------------------------------------------------------------------------- 1 | """We have a Modal""" 2 | import panel as pn 3 | 4 | from panel_modal import Modal 5 | 6 | 7 | def test_constructor(): 8 | """We can construct and work with the Modal""" 9 | modal = Modal(pn.panel("Hi. I am the Panel Modal!", width=200)) 10 | modal.param.trigger("open") 11 | modal.is_open = True 12 | modal.param.trigger("close") 13 | modal.is_open = False 14 | --------------------------------------------------------------------------------