diff --git a/vizro-core/changelog.d/20231201_152948_nadija_ratkusic_graca_navigation_css_refinements.md b/vizro-core/changelog.d/20231201_152948_nadija_ratkusic_graca_navigation_css_refinements.md new file mode 100644 index 000000000..403407a16 --- /dev/null +++ b/vizro-core/changelog.d/20231201_152948_nadija_ratkusic_graca_navigation_css_refinements.md @@ -0,0 +1,47 @@ + + + + + +### Added + +- Enable tooltips for `NavLink`. ([#186](https://github.com/mckinsey/vizro/pull/186)) + + + + + diff --git a/vizro-core/docs/assets/user_guides/navigation/accordion_inside_nav_bar.png b/vizro-core/docs/assets/user_guides/navigation/accordion_inside_nav_bar.png index caf529a84..27dce9979 100644 Binary files a/vizro-core/docs/assets/user_guides/navigation/accordion_inside_nav_bar.png and b/vizro-core/docs/assets/user_guides/navigation/accordion_inside_nav_bar.png differ diff --git a/vizro-core/docs/assets/user_guides/navigation/custom_icons.png b/vizro-core/docs/assets/user_guides/navigation/custom_icons.png index 19a456bf1..bc14546f4 100644 Binary files a/vizro-core/docs/assets/user_guides/navigation/custom_icons.png and b/vizro-core/docs/assets/user_guides/navigation/custom_icons.png differ diff --git a/vizro-core/docs/assets/user_guides/navigation/nav_bar.png b/vizro-core/docs/assets/user_guides/navigation/nav_bar.png index e2715cdcb..5189386d1 100644 Binary files a/vizro-core/docs/assets/user_guides/navigation/nav_bar.png and b/vizro-core/docs/assets/user_guides/navigation/nav_bar.png differ diff --git a/vizro-core/docs/pages/user_guides/navigation.md b/vizro-core/docs/pages/user_guides/navigation.md index fd9a00166..f84efe438 100644 --- a/vizro-core/docs/pages/user_guides/navigation.md +++ b/vizro-core/docs/pages/user_guides/navigation.md @@ -174,7 +174,7 @@ Another way to group together pages in the navigation is to use a [`NavBar`][viz [NavBar]: ../../assets/user_guides/navigation/nav_bar.png -Here, the first level of the navigation hierarchy ("Group A" and "Group B") is represented by an icon in a navigation bar, and the second level of the navigation (the pages) is represented by an accordion. By default, the icons are the [`filter` icons from the Google Material icons library](https://fonts.google.com/icons?icon.query=filter). +Here, the first level of the navigation hierarchy ("Group A" and "Group B") is represented by an icon in a navigation bar, and the second level of the navigation (the pages) is represented by an accordion. By default, the icons are the [`filter` icons from the Google Material icons library](https://fonts.google.com/icons?icon.query=filter). The icon label ("Group A" and "Group B") appears as a tooltip on hovering over the icon. ## Customizing the navigation bar diff --git a/vizro-core/examples/default/app.py b/vizro-core/examples/default/app.py index 3bcce552b..f8b2e3325 100644 --- a/vizro-core/examples/default/app.py +++ b/vizro-core/examples/default/app.py @@ -526,7 +526,8 @@ def create_home_page(): pages={ "Analysis": ["Homepage", "Variable Analysis", "Relationship Analysis", "Country Analysis"], "Summary": ["Continent Summary"], - } + }, + nav_selector=vm.NavBar(), ), ) diff --git a/vizro-core/pyproject.toml b/vizro-core/pyproject.toml index 26747a3bf..f5088da68 100644 --- a/vizro-core/pyproject.toml +++ b/vizro-core/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "pandas", "pydantic>=1.10.13", # must be synced with pre-commit mypy hook manually "dash_daq", + "dash_mantine_components", "ipython>=8.10.0", # not directly required, pinned by Snyk to avoid a vulnerability: https://app.snyk.io/vuln/SNYK-PYTHON-IPYTHON-3318382 "numpy>=1.22.2", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-NUMPY-2321970 "tornado>=6.3.2", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-TORNADO-5537286 diff --git a/vizro-core/snyk/requirements.txt b/vizro-core/snyk/requirements.txt index 5d2fe4b32..81b4e6dbe 100644 --- a/vizro-core/snyk/requirements.txt +++ b/vizro-core/snyk/requirements.txt @@ -3,6 +3,7 @@ dash_bootstrap_components pandas pydantic>=1.10.13 dash_daq +dash_mantine_components ipython>=8.10.0 numpy>=1.22.2 tornado>=6.3.2 diff --git a/vizro-core/src/vizro/models/__init__.py b/vizro-core/src/vizro/models/__init__.py index e167468e0..4db1a2c70 100644 --- a/vizro-core/src/vizro/models/__init__.py +++ b/vizro-core/src/vizro/models/__init__.py @@ -22,6 +22,7 @@ # Please keep alphabetically ordered __all__ = [ + "Accordion", "Action", "Button", "Card", diff --git a/vizro-core/src/vizro/models/_navigation/accordion.py b/vizro-core/src/vizro/models/_navigation/accordion.py index d7a44d11b..724dda7d6 100644 --- a/vizro-core/src/vizro/models/_navigation/accordion.py +++ b/vizro-core/src/vizro/models/_navigation/accordion.py @@ -52,7 +52,7 @@ def build(self, *, active_page_id=None): dbc.AccordionItem( children=accordion_buttons, title=page_group.upper(), - class_name="accordion_item", + class_name="accordion-item-header", ) ) diff --git a/vizro-core/src/vizro/models/_navigation/nav_link.py b/vizro-core/src/vizro/models/_navigation/nav_link.py index bbf8864c1..319addaa2 100644 --- a/vizro-core/src/vizro/models/_navigation/nav_link.py +++ b/vizro-core/src/vizro/models/_navigation/nav_link.py @@ -4,6 +4,7 @@ import dash import dash_bootstrap_components as dbc +import dash_mantine_components as dmc from dash import html try: @@ -61,10 +62,13 @@ def build(self, *, active_page_id=None): button = dbc.Button( [ - html.Span(self.icon, className="material-symbols-outlined"), - # TODO: commented out until we insert styling for the tooltip or find a better way to display it (e.g. - # try dbc.Popover or Dash mantine components tooltip?). - # dbc.Tooltip(html.P(self.label), target=self.id, placement="bottom", className="custom-tooltip"), + dmc.Tooltip( + label=self.label, + offset=4, + withArrow=True, + children=[html.Span(self.icon, className="material-symbols-outlined")], + position="bottom-start", + ) ], id=self.id, className="icon-button", diff --git a/vizro-core/src/vizro/static/css/layout.css b/vizro-core/src/vizro/static/css/layout.css index 7b9a0e29d..33d25a5ef 100644 --- a/vizro-core/src/vizro/static/css/layout.css +++ b/vizro-core/src/vizro/static/css/layout.css @@ -117,7 +117,7 @@ .icon-button { background-color: var(--surfaces-bg-02); - width: 64px; + width: 100%; height: 64px; display: flex; align-items: center; @@ -131,32 +131,28 @@ div.dashboard_container .tooltip-inner { div.dashboard_container .custom-tooltip { color: var(--text-primary); } -.icon-text { - font-size: var(--text-size-02); - letter-spacing: var(--letter-spacing-body-edit-01); - color: var(--text-secondary); - padding: 4px; -} - -.nav-icon { - filter: var(--fill-accordion-button); - width: 24px; - height: 24px; -} - -.nav-icon-text { - display: flex; - flex-direction: column; - align-items: center; - gap: 6px; - justify-content: center; -} -.icon-button.btn.btn-primary.active { - background-color: var(--state-overlays-selected); +.icon-button.btn.btn-primary.active .material-symbols-outlined { + color: var(--text-active); } - .loading-container { height: 100%; width: 100%; } + +.mantine-Tooltip-tooltip { + font-size: var(--text-size-01); + font-weight: var(--text-weight-light); + line-height: 16px; + letter-spacing: var(--letter-spacing-help-text); + padding: var(--spacing-01) var(--spacing-02); + color: var(--tooltip-text-primary); + background-color: var(--border-selected); + max-width: 180px; + overflow-wrap: break-word; + text-wrap: wrap; + filter: drop-shadow(0px 2px 2px #141721); + border-radius: 0; + box-shadow: var(--box-shadow-elevation-tooltip-hover); + white-space: pre-wrap; +} diff --git a/vizro-core/src/vizro/static/css/slider.css b/vizro-core/src/vizro/static/css/slider.css index 31d171387..8d43bf480 100644 --- a/vizro-core/src/vizro/static/css/slider.css +++ b/vizro-core/src/vizro/static/css/slider.css @@ -134,3 +134,7 @@ justify-content: space-between; width: 100%; } + +input.dash-input:invalid { + outline: none; +} diff --git a/vizro-core/src/vizro/static/css/variables.css b/vizro-core/src/vizro/static/css/variables.css index 9da41f8fb..09ad4b158 100644 --- a/vizro-core/src/vizro/static/css/variables.css +++ b/vizro-core/src/vizro/static/css/variables.css @@ -85,6 +85,9 @@ --state-overlays-selected-hover: var( --state-overlays-dark-mode-selected-hover ); + --tooltip-text-primary: rgba(20, 23, 33, 0.88); + --box-shadow-elevation-tooltip-hover: 0px 4px 8px 0px rgba(20, 23, 33, 0.38), + 0px 2px 4px -1px rgba(20, 23, 33, 0.88); } .vizro_light { @@ -143,4 +146,7 @@ --state-overlays-selected-hover: var( --state-overlays-light-mode-selected-hover ); + --tooltip-text-primary: rgba(255, 255, 255, 0.88); + --box-shadow-elevation-tooltip-hover: 0px 4px 8px 0px rgba(20, 23, 33, 0.12), + 0px 2px 4px -1px rgba(20, 23, 33, 0.08); } diff --git a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_bar.py b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_bar.py index 734fc4020..93fd01544 100644 --- a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_bar.py +++ b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_bar.py @@ -2,6 +2,7 @@ import re import dash_bootstrap_components as dbc +import dash_mantine_components as dmc import pytest from asserts import assert_component_equal from dash import html @@ -77,11 +78,21 @@ def test_items_with_with_pages_icons(self, pages_as_dict): class TestNavBarBuildMethod: """Tests NavBar model build method.""" + common_args = {"offset": 4, "withArrow": True, "position": "bottom-start"} + def test_nav_bar_active_pages_as_dict(self, pages_as_dict): nav_bar = vm.NavBar(pages=pages_as_dict) nav_bar.pre_build() built_nav_bar = nav_bar.build(active_page_id="Page 1") - expected_button = html.Div([dbc.Button(children=[html.Span(children="filter_1")], active=True, href="/")]) + expected_button = html.Div( + [ + dbc.Button( + children=[dmc.Tooltip(label="Group", children=[html.Span("filter_1")], **self.common_args)], + active=True, + href="/", + ) + ] + ) assert_component_equal(built_nav_bar["nav_bar_outer"], expected_button) assert_component_equal( built_nav_bar["nav_panel_outer"], html.Div(id="nav_panel_outer"), keys_to_strip={"children", "className"} @@ -94,8 +105,16 @@ def test_nav_bar_active_pages_as_list(self, pages_as_list): built_nav_bar = nav_bar.build(active_page_id="Page 1") expected_buttons = html.Div( [ - dbc.Button(children=[html.Span(children="filter_1")], active=True, href="/"), - dbc.Button(children=[html.Span(children="filter_2")], active=False, href="/page-2"), + dbc.Button( + children=[dmc.Tooltip(label="Page 1", children=[html.Span("filter_1")], **self.common_args)], + active=True, + href="/", + ), + dbc.Button( + children=[dmc.Tooltip(label="Page 2", children=[html.Span("filter_2")], **self.common_args)], + active=False, + href="/page-2", + ), ] ) assert_component_equal(built_nav_bar["nav_bar_outer"], expected_buttons) @@ -109,7 +128,15 @@ def test_nav_bar_not_active_pages_as_dict(self, pages_as_dict): nav_bar = vm.NavBar(pages=pages_as_dict) nav_bar.pre_build() built_nav_bar = nav_bar.build(active_page_id="Page 3") - expected_button = html.Div([dbc.Button(children=[html.Span(children="filter_1")], active=False, href="/")]) + expected_button = html.Div( + [ + dbc.Button( + children=[dmc.Tooltip(label="Group", children=[html.Span("filter_1")], **self.common_args)], + active=False, + href="/", + ) + ] + ) assert_component_equal(built_nav_bar["nav_bar_outer"], expected_button) assert_component_equal( built_nav_bar["nav_panel_outer"], html.Div(hidden=True, id="nav_panel_outer"), keys_to_strip={} @@ -121,8 +148,16 @@ def test_nav_bar_not_active_pages_as_list(self, pages_as_list): built_nav_bar = nav_bar.build(active_page_id="Page 3") expected_buttons = html.Div( [ - dbc.Button(children=[html.Span(children="filter_1")], active=False, href="/"), - dbc.Button(children=[html.Span(children="filter_2")], active=False, href="/page-2"), + dbc.Button( + children=[dmc.Tooltip(label="Page 1", children=[html.Span("filter_1")], **self.common_args)], + active=False, + href="/", + ), + dbc.Button( + children=[dmc.Tooltip(label="Page 2", children=[html.Span("filter_2")], **self.common_args)], + active=False, + href="/page-2", + ), ] ) assert_component_equal(built_nav_bar["nav_bar_outer"], expected_buttons) diff --git a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_item.py b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_link.py similarity index 87% rename from vizro-core/tests/unit/vizro/models/_navigation/test_nav_item.py rename to vizro-core/tests/unit/vizro/models/_navigation/test_nav_link.py index 50243a19d..c02d55247 100644 --- a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_item.py +++ b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_link.py @@ -2,6 +2,7 @@ import re import dash_bootstrap_components as dbc +import dash_mantine_components as dmc import pytest from asserts import assert_component_equal from dash import html @@ -72,12 +73,18 @@ def test_nav_link(self, pages_as_dict): class TestNavLinkBuildMethod: """Tests NavLink model build method.""" + common_args = {"offset": 4, "withArrow": True, "position": "bottom-start"} + def test_nav_link_active(self, pages, request): pages = request.getfixturevalue(pages) nav_link = vm.NavLink(id="nav_link", label="Label", icon="icon", pages=pages) nav_link.pre_build() built_nav_link = nav_link.build(active_page_id="Page 1") - expected_button = dbc.Button(id="nav_link", children=[html.Span("icon")], active=True, href="/") + expected_button = dbc.Button( + children=[dmc.Tooltip(label="Label", children=[html.Span("icon")], **self.common_args)], + active=True, + href="/", + ) assert_component_equal(built_nav_link["nav_link"], expected_button) assert all(isinstance(child, dbc.Accordion) for child in built_nav_link["nav_panel_outer"].children) @@ -86,6 +93,10 @@ def test_nav_link_not_active(self, pages, request): nav_link = vm.NavLink(id="nav_link", label="Label", icon="icon", pages=pages) nav_link.pre_build() built_nav_link = nav_link.build(active_page_id="Page 3") - expected_button = dbc.Button(id="nav_link", children=[html.Span("icon")], active=False, href="/") + expected_button = dbc.Button( + children=[dmc.Tooltip(label="Label", children=[html.Span("icon")], **self.common_args)], + active=False, + href="/", + ) assert_component_equal(built_nav_link["nav_link"], expected_button) assert "nav_panel_outer" not in built_nav_link