Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
crossorigin="anonymous"
/>
</head>
Expand Down
4 changes: 4 additions & 0 deletions docs/components_page/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def register_apps():
},
"nav": {"markdown_path": COMPONENTS / "nav.md"},
"navbar": {"markdown_path": COMPONENTS / "navbar.md"},
"offcanvas": {
"markdown_path": COMPONENTS / "offcanvas.md",
},
"pagination": {"markdown_path": COMPONENTS / "pagination.md"},
"popover": {"markdown_path": COMPONENTS / "popover.md"},
"progress": {"markdown_path": COMPONENTS / "progress.md"},
"spinner": {"markdown_path": COMPONENTS / "spinner.md"},
Expand Down
197 changes: 197 additions & 0 deletions docs/components_page/components/__tests__/test_offcanvas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
"""
Testing of callbacks in non-Python Offcanvas snippets.
"""
from pathlib import Path

import dash.testing.wait as wait

from .helpers import load_jl_app, load_r_app

HERE = Path(__file__).parent


def test_r_offcanvas_simple(dashr):
r_app = load_r_app((HERE.parent / "offcanvas" / "simple.R"), "offcanvas")
dashr.start_server(r_app)
check_offcanvas_simple_callbacks(dashr)


def test_jl_offcanvas_simple(dashjl):
jl_app = load_jl_app(
(HERE.parent / "offcanvas" / "simple.jl"), "offcanvas"
)
dashjl.start_server(jl_app)
check_offcanvas_simple_callbacks(dashjl)


def check_offcanvas_simple_callbacks(runner):
runner.find_element("#open-offcanvas").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) != 0,
timeout=4,
)

# When offcanvas-header.btn-close is clicked, the offcanvas disappears
runner.find_element(".offcanvas-header .btn-close").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) == 0,
timeout=4,
)

runner.find_element("#open-offcanvas").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) != 0,
timeout=4,
)

# When modal-backdrop (to change to offcanvas-backdrop) is clicked,
# the offcanvas disappears
# TODO: Once react-bootstrap has fixed this, need to change to
# offcanvas-backdrop
runner.find_element(".modal-backdrop").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) == 0,
timeout=4,
)


# ------------------------------


def test_r_offcanvas_backdrop(dashr):
r_app = load_r_app((HERE.parent / "offcanvas" / "backdrop.R"), "offcanvas")
dashr.start_server(r_app)
check_offcanvas_backdrop_callbacks(dashr)


def test_jl_offcanvas_backdrop(dashjl):
jl_app = load_jl_app(
(HERE.parent / "offcanvas" / "backdrop.jl"), "offcanvas"
)
dashjl.start_server(jl_app)
check_offcanvas_backdrop_callbacks(dashjl)


def check_offcanvas_backdrop_callbacks(runner):
runner.find_element("#open-offcanvas-backdrop").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) != 0,
timeout=4,
)

runner.find_element("#offcanvas-backdrop-selector").selectByValue(False)
wait.until(
# TODO: Once react-bootstrap has fixed this, need to change to
# offcanvas-backdrop
lambda: len(runner.find_elements(".modal-backdrop")) == 0,
timeout=4,
)

runner.find_element("#offcanvas-backdrop-selector").selectByValue(True)
wait.until(
# TODO: Once react-bootstrap has fixed this, need to change to
# offcanvas-backdrop
lambda: len(runner.find_elements(".modal-backdrop")) != 0,
timeout=4,
)

runner.find_element("#offcanvas-backdrop-selector").selectByValue(False)
wait.until(
# TODO: Once react-bootstrap has fixed this, need to change to
# offcanvas-backdrop
lambda: len(runner.find_elements(".modal-backdrop")) == 0,
timeout=4,
)

runner.find_element("#offcanvas-backdrop-selector").selectByValue("static")
wait.until(
# TODO: Once react-bootstrap has fixed this, need to change to
# offcanvas-backdrop
lambda: len(runner.find_elements(".modal-backdrop")) != 0,
timeout=4,
)

runner.find_element("#close-offcanvas-backdrop").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) == 0,
timeout=4,
)


# ------------------------------


def test_r_offcanvas_scrollable(dashr):
r_app = load_r_app(
(HERE.parent / "offcanvas" / "scrollable.R"), "offcanvas"
)
dashr.start_server(r_app)
check_offcanvas_scrollable_callbacks(dashr)


def test_jl_offcanvas_scrollable(dashjl):
jl_app = load_jl_app(
(HERE.parent / "offcanvas" / "scrollable.jl"), "offcanvas"
)
dashjl.start_server(jl_app)
check_offcanvas_scrollable_callbacks(dashjl)


def check_offcanvas_scrollable_callbacks(runner):
import time

time.sleep(3)
runner.find_element("#open-offcanvas-scrollable").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) != 0,
timeout=4,
)

runner.find_element("#close-offcanvas-scrollable").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) == 0,
timeout=4,
)


# ------------------------------


def test_r_offcanvas_placement(dashr):
r_app = load_r_app(
(HERE.parent / "offcanvas" / "placement.R"), "offcanvas"
)
dashr.start_server(r_app)
check_offcanvas_placement_callbacks(dashr)


def test_jl_offcanvas_placement(dashjl):
jl_app = load_jl_app(
(HERE.parent / "offcanvas" / "placement.jl"), "offcanvas"
)
dashjl.start_server(jl_app)
check_offcanvas_placement_callbacks(dashjl)


def check_offcanvas_placement_callbacks(runner):
runner.find_element("#open-offcanvas-placement").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) != 0,
timeout=4,
)

# Changing placement
for option in ["end", "top", "bottom", "start"]:
runner.find_element("#offcanvas-placement-selector").selectByValue(
option
)
wait.until(
lambda: len(runner.find_elements(f".offcanvas-{option}")) != 0,
timeout=4,
)

runner.find_element("#close-offcanvas-placement").click()
wait.until(
lambda: len(runner.find_elements(".offcanvas")) == 0,
timeout=4,
)
67 changes: 67 additions & 0 deletions docs/components_page/components/__tests__/test_pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Testing of callbacks in non-Python Pagination snippets.
"""
from pathlib import Path

import dash.testing.wait as wait

from .helpers import load_jl_app, load_r_app

HERE = Path(__file__).parent


def test_r_pagination_simple(dashr):
r_app = load_r_app((HERE.parent / "pagination" / "simple.R"), "pagination")
dashr.start_server(r_app)
check_pagination_simple_callbacks(dashr)


def test_jl_pagination_simple(dashjl):
jl_app = load_jl_app(
(HERE.parent / "pagination" / "simple.jl"), "pagination"
)
dashjl.start_server(jl_app)
check_pagination_simple_callbacks(dashjl)


def check_pagination_simple_callbacks(runner):

# Find the pagination object
pagination_comp = runner.find_element("#pagination")
pagination_text = runner.find_element("#pagination-contents")

# Check it has 10 page-items objects in it
pages = pagination_comp.find_elements(".page-item")
wait.until(
lambda: len(pages) == 10,
timeout=4,
)

# Click the link with text 7
wait.until(
lambda: pages[6].text == "7",
timeout=4,
)
pages[6].click()

# Check the text in contents changes to "Page selected: 7"
wait.until(
lambda: pagination_text.text == "Page selected: 7",
timeout=4,
)

# Change the slider to value 5
runner.click_at_coord_fractions(
runner.find_element("#page-change"), 0.5, 0.25
)

# Check the text in contents changes to "Page selected: 5"
wait.until(
lambda: pagination_text.text == "Page selected: 5",
timeout=4,
)

# Check that the <li> object inside pagination with number = 5
# has active as a class
pages = pagination_comp.find_element(".active")
wait.until(lambda: pages[4].text == "5")
2 changes: 1 addition & 1 deletion docs/components_page/components/__tests__/test_snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
ENVS = {
"modal.md": {
"LOREM": (HERE.parent / "modal" / "lorem.txt").read_text().strip()
}
},
}

R_PORT = 8051
Expand Down
28 changes: 28 additions & 0 deletions docs/components_page/components/offcanvas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: Offcanvas
lead: Use the `Offcanvas` component to add a customisable sidebar to your apps.
---

`Offcanvas` components work in a similar fashion to a simplified `Modal`. Set the `is_open` prop of the `Offcanvas` to `True` to open the offcanvas. By default, the offcanvas can be dismissed by clicking the close button in the header, outside the offcanvas or by pressing the escape key (these behaviours can all be overridden, using `close_button=False`, `backdrop="static"` and `keyboard=False` respectively - see below), though you can also write your own callbacks that set `is_open` to `False`.

{{example:components/offcanvas/simple.py:offcanvas}}

## Placement

By default the offcanvas will appear to the left of the screen (`start`). You can change where it appears though by using the `placement` property.

{{example:components/offcanvas/placement.py:offcanvas}}

## Backdrop

By default the offcanvas will render with a backdrop that dismisses the offcanvas on click. Set `backdrop=False` to render the offcanvas without a backdrop, or `backdrop="static"` to render a backdrop that doesn't dismiss the offcanvas when clicked.

{{example:components/offcanvas/backdrop.py:offcanvas}}

## Scrolling main page contents

By default, when an offcanvas is displaying, the user is unable to scroll content on the main page. If you prefer you can specify `scrollable=True` to allow the user to still scroll the content whilst the offcanvas is showing.

{{example:components/offcanvas/scrollable.py:offcanvas}}

{{apidoc:src/components/offcanvas/Offcanvas.js}}
68 changes: 68 additions & 0 deletions docs/components_page/components/offcanvas/backdrop.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
library(dashBootstrapComponents)
library(dashHtmlComponents)

offcanvas <- htmlDiv(
list(
dbcButton("Open backdrop offcanvas", id="open-offcanvas-backdrop", n_clicks=0),
dbcOffcanvas(
list(
htmlDiv(
"Change the backdrop of this offcanvas with the "
"radio buttons"
),
dbcFormGroup(
list(
dbcRadioItems(
id="offcanvas-backdrop-selector",
options=list(
list(
label="True (default)", value=TRUE,
),
list(label="False", value=TRUE),
list(
label="Static (no dismiss)", value="static"
),
),
inline=TRUE,
value=TRUE,
),
),
class_name="p-3 m-2 border",
),
dbcButton(
"Close",
id="close-offcanvas-backdrop",
class_name="ml-auto",
n_clicks=0,
),
),
id="offcanvas-backdrop",
title="Offcanvas with/without backdrop"
),
)
)


app$callback(
output("offcanvas-backdrop", "backdrop"),
list(input("offcanvas-backdrop-selector", "value")),
function(backdrop) {
return(backdrop)
}
)


app$callback(
output("offcanvas-backdrop", "is_open"),
list(
input("open-offcanvas-backdrop", "n_clicks"),
input("close-offcanvas-backdrop", "n_clicks"),
state("offcanvas-backdrop", "is_open")
),
function(n1, n2, is_open) {
if (n1 > 0 | n2 > 0) {
return(!is_open)
}
return(is_open)
}
)
Loading