mirror of
https://github.com/simonw/datasette.git
synced 2026-10-20 08:17:00 +02:00
Screenshots for reusable modal dialogs docs, plus just shots (#2958)
docs/shots.yml defines documentation screenshots for shot-scraper "Just shots" runs them.
This commit is contained in:
parent
8e17729ff3
commit
cec5e6b2ef
9 changed files with 157 additions and 41 deletions
7
Justfile
7
Justfile
|
|
@ -49,13 +49,18 @@ export DATASETTE_SECRET := "not_a_secret"
|
||||||
uv run cog -r README.md docs/*.rst
|
uv run cog -r README.md docs/*.rst
|
||||||
|
|
||||||
# Serve live docs on localhost:8000
|
# Serve live docs on localhost:8000
|
||||||
@docs: cog blacken-docs
|
@docs: shots cog blacken-docs
|
||||||
uv run make -C docs livehtml
|
uv run make -C docs livehtml
|
||||||
|
|
||||||
# Build docs as static HTML
|
# Build docs as static HTML
|
||||||
@docs-build: cog blacken-docs
|
@docs-build: cog blacken-docs
|
||||||
rm -rf docs/_build && cd docs && uv run make html
|
rm -rf docs/_build && cd docs && uv run make html
|
||||||
|
|
||||||
|
# Take any missing documentation screenshots defined in docs/shots.yml
|
||||||
|
@shots:
|
||||||
|
uv run --group shots shot-scraper install
|
||||||
|
cd docs && uv run --group shots shot-scraper multi shots.yml --no-clobber --reduced-motion --retina
|
||||||
|
|
||||||
# Apply Black
|
# Apply Black
|
||||||
@black:
|
@black:
|
||||||
uv run black datasette tests
|
uv run black datasette tests
|
||||||
|
|
|
||||||
|
|
@ -312,6 +312,19 @@ To update these pages, run the following command::
|
||||||
|
|
||||||
uv run cog -r docs/*.rst
|
uv run cog -r docs/*.rst
|
||||||
|
|
||||||
|
.. _contributing_documentation_screenshots:
|
||||||
|
|
||||||
|
Documentation screenshots
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Screenshots in the documentation are defined in ``docs/shots.yml`` and taken using `shot-scraper <https://shot-scraper.datasette.io/>`__. That file starts a Datasette server that loads JavaScript from ``docs/shots/``, then saves each screenshot as a WebP image in ``docs/images/``.
|
||||||
|
|
||||||
|
To take any screenshots that do not exist yet, run::
|
||||||
|
|
||||||
|
just shots
|
||||||
|
|
||||||
|
``just docs`` runs this too. Existing images are skipped. To replace a screenshot, delete its image file and run ``just shots`` again.
|
||||||
|
|
||||||
.. _contributing_template_contexts:
|
.. _contributing_template_contexts:
|
||||||
|
|
||||||
Documented template contexts
|
Documented template contexts
|
||||||
|
|
|
||||||
BIN
docs/images/modal-classes.webp
Normal file
BIN
docs/images/modal-classes.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/images/modal-example.webp
Normal file
BIN
docs/images/modal-example.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -488,46 +488,16 @@ Creating a dialog
|
||||||
|
|
||||||
This example uses the :ref:`datasette_init event <javascript_datasette_init>` to add a button that opens a dialog:
|
This example uses the :ref:`datasette_init event <javascript_datasette_init>` to add a button that opens a dialog:
|
||||||
|
|
||||||
.. code-block:: javascript
|
.. literalinclude:: shots/modal-example.js
|
||||||
|
:language: javascript
|
||||||
|
|
||||||
document.addEventListener("datasette_init", () => {
|
Clicking that button opens this dialog:
|
||||||
const openButton = document.createElement("button");
|
|
||||||
openButton.type = "button";
|
|
||||||
openButton.textContent = "Open example dialog";
|
|
||||||
// Indicate that this button opens a dialog:
|
|
||||||
openButton.setAttribute("aria-haspopup", "dialog");
|
|
||||||
// Identify which dialog it controls:
|
|
||||||
openButton.setAttribute("aria-controls", "my-plugin-dialog");
|
|
||||||
|
|
||||||
const modal = DatasetteModal.create();
|
.. only:: not latex
|
||||||
const dialog = modal.dialog;
|
|
||||||
dialog.id = "my-plugin-dialog";
|
|
||||||
// Tell screenreaders the dialog is labelled by #my-plugin-dialog-title
|
|
||||||
dialog.setAttribute("aria-labelledby", "my-plugin-dialog-title");
|
|
||||||
dialog.innerHTML = `
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 class="modal-title" id="my-plugin-dialog-title">
|
|
||||||
Example dialog
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
This dialog uses Datasette's shared styles and keyboard behavior.
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="modal-btn modal-btn-ghost">Close</button>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
const closeButton = dialog.querySelector("button");
|
.. image:: images/modal-example.webp
|
||||||
closeButton.addEventListener("click", () => {
|
:width: 584px
|
||||||
modal.requestClose("cancel");
|
:alt: A dialog titled Example dialog, with the text "This dialog uses Datasette's shared styles and keyboard behavior." and a Close button in the footer, shown in front of a dimmed Datasette page
|
||||||
});
|
|
||||||
openButton.addEventListener("click", () => {
|
|
||||||
modal.show({ returnFocusTo: openButton, initialFocus: closeButton });
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.append(modal);
|
|
||||||
document.querySelector("section.content").append(openButton);
|
|
||||||
});
|
|
||||||
|
|
||||||
Opening and closing
|
Opening and closing
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -584,7 +554,15 @@ If an operation fails, set ``modal.busy = false`` so the user can retry or close
|
||||||
Shared CSS classes
|
Shared CSS classes
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The classes in the example above provide built-in styling. The following classes can be used by your modal:
|
The classes in the example above provide built-in styling. This dialog uses every class listed below, including a ``modal-meta`` count in the header and ``footer-info`` text next to ``modal-btn-ghost`` and ``modal-btn-primary`` buttons in the footer:
|
||||||
|
|
||||||
|
.. only:: not latex
|
||||||
|
|
||||||
|
.. image:: images/modal-classes.webp
|
||||||
|
:width: 584px
|
||||||
|
:alt: A dialog titled Export rows with a "3 selected" badge in its header, a list of three plant names in the body, and a footer containing the text "CSV, UTF-8", a Cancel button and a blue Export button
|
||||||
|
|
||||||
|
The following classes can be used by your modal:
|
||||||
|
|
||||||
``datasette-modal``
|
``datasette-modal``
|
||||||
Added automatically to the native ``<dialog>`` when the wrapper is connected to the page. Provides the dialog's sizing, background, rounded corners, shadow, backdrop and animations.
|
Added automatically to the native ``<dialog>`` when the wrapper is connected to the page. Provides the dialog's sizing, background, rounded corners, shadow, backdrop and animations.
|
||||||
|
|
|
||||||
38
docs/shots.yml
Normal file
38
docs/shots.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Screenshots used by the documentation, taken using shot-scraper:
|
||||||
|
# https://shot-scraper.datasette.io/en/stable/multi.html
|
||||||
|
#
|
||||||
|
# Run "just shots" from the repository root to create any that are
|
||||||
|
# missing. Existing images are skipped, so delete an image to recreate it.
|
||||||
|
#
|
||||||
|
# Paths are relative to this docs/ directory.
|
||||||
|
|
||||||
|
# Serves the JavaScript in docs/shots/ and loads it on every page.
|
||||||
|
# List form means the datasette process is stopped directly when done.
|
||||||
|
- server:
|
||||||
|
- datasette
|
||||||
|
- --memory
|
||||||
|
- --port
|
||||||
|
- 8755
|
||||||
|
- --static
|
||||||
|
- shots:shots
|
||||||
|
- -s
|
||||||
|
- extra_js_urls
|
||||||
|
- '["/shots/modal-example.js", "/shots/modal-classes.js"]'
|
||||||
|
|
||||||
|
# javascript_plugins.rst - Reusable modal dialogs
|
||||||
|
- output: images/modal-example.webp
|
||||||
|
url: http://localhost:8755/
|
||||||
|
javascript: |
|
||||||
|
document.querySelector('[aria-controls="my-plugin-dialog"]').click();
|
||||||
|
selector: "#my-plugin-dialog"
|
||||||
|
padding: 32
|
||||||
|
quality: 70
|
||||||
|
|
||||||
|
- output: images/modal-classes.webp
|
||||||
|
url: http://localhost:8755/
|
||||||
|
javascript: |
|
||||||
|
document.querySelector('[aria-controls="export-dialog"]').click();
|
||||||
|
document.activeElement.blur();
|
||||||
|
selector: "#export-dialog"
|
||||||
|
padding: 32
|
||||||
|
quality: 70
|
||||||
41
docs/shots/modal-classes.js
Normal file
41
docs/shots/modal-classes.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Demonstrates every shared modal CSS class, for images/modal-classes.webp
|
||||||
|
document.addEventListener("datasette_init", () => {
|
||||||
|
const openButton = document.createElement("button");
|
||||||
|
openButton.type = "button";
|
||||||
|
openButton.textContent = "Open export dialog";
|
||||||
|
openButton.setAttribute("aria-haspopup", "dialog");
|
||||||
|
openButton.setAttribute("aria-controls", "export-dialog");
|
||||||
|
|
||||||
|
const modal = DatasetteModal.create();
|
||||||
|
const dialog = modal.dialog;
|
||||||
|
dialog.id = "export-dialog";
|
||||||
|
dialog.setAttribute("aria-labelledby", "export-dialog-title");
|
||||||
|
dialog.innerHTML = `
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title" id="export-dialog-title">Export rows</h2>
|
||||||
|
<span class="modal-meta">3 selected</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Export these rows from the <strong>plants</strong> table as CSV:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Monstera deliciosa</li>
|
||||||
|
<li>Ficus lyrata</li>
|
||||||
|
<li>Pilea peperomioides</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<span class="footer-info">CSV, UTF-8</span>
|
||||||
|
<button type="button" class="modal-btn modal-btn-ghost">Cancel</button>
|
||||||
|
<button type="button" class="modal-btn modal-btn-primary">Export</button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
const [cancelButton, exportButton] = dialog.querySelectorAll(".modal-footer button");
|
||||||
|
cancelButton.addEventListener("click", () => modal.requestClose("cancel"));
|
||||||
|
exportButton.addEventListener("click", () => modal.close());
|
||||||
|
openButton.addEventListener("click", () => {
|
||||||
|
modal.show({ returnFocusTo: openButton, initialFocus: exportButton });
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.append(modal);
|
||||||
|
document.querySelector("section.content").append(openButton);
|
||||||
|
});
|
||||||
38
docs/shots/modal-example.js
Normal file
38
docs/shots/modal-example.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
document.addEventListener("datasette_init", () => {
|
||||||
|
const openButton = document.createElement("button");
|
||||||
|
openButton.type = "button";
|
||||||
|
openButton.textContent = "Open example dialog";
|
||||||
|
// Indicate that this button opens a dialog:
|
||||||
|
openButton.setAttribute("aria-haspopup", "dialog");
|
||||||
|
// Identify which dialog it controls:
|
||||||
|
openButton.setAttribute("aria-controls", "my-plugin-dialog");
|
||||||
|
|
||||||
|
const modal = DatasetteModal.create();
|
||||||
|
const dialog = modal.dialog;
|
||||||
|
dialog.id = "my-plugin-dialog";
|
||||||
|
// Tell screenreaders the dialog is labelled by #my-plugin-dialog-title
|
||||||
|
dialog.setAttribute("aria-labelledby", "my-plugin-dialog-title");
|
||||||
|
dialog.innerHTML = `
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title" id="my-plugin-dialog-title">
|
||||||
|
Example dialog
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
This dialog uses Datasette's shared styles and keyboard behavior.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="modal-btn modal-btn-ghost">Close</button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
const closeButton = dialog.querySelector("button");
|
||||||
|
closeButton.addEventListener("click", () => {
|
||||||
|
modal.requestClose("cancel");
|
||||||
|
});
|
||||||
|
openButton.addEventListener("click", () => {
|
||||||
|
modal.show({ returnFocusTo: openButton, initialFocus: closeButton });
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.append(modal);
|
||||||
|
document.querySelector("section.content").append(openButton);
|
||||||
|
});
|
||||||
|
|
@ -87,6 +87,9 @@ dev = [
|
||||||
playwright = [
|
playwright = [
|
||||||
"pytest-playwright>=0.8.0",
|
"pytest-playwright>=0.8.0",
|
||||||
]
|
]
|
||||||
|
shots = [
|
||||||
|
"shot-scraper>=1.12",
|
||||||
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
rich = ["rich"]
|
rich = ["rich"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue