From d23cf0d4ddb4a79cc1a3328f1b0032644d741cf4 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 29 Apr 2026 23:22:44 -0400 Subject: [PATCH 1/3] fix: enforce explicit colors on sidebar panels ipyvue 1.12.0 inherits Vuetify theme colors that can render as light text on light backgrounds, making sidebar panel headers and contents unreadable. Pin background and foreground colors on the sidebar toggle and settings buttons, layer manager, and custom widget panels so the UI remains legible regardless of the host theme. --- leafmap/maplibregl.py | 105 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 19 deletions(-) diff --git a/leafmap/maplibregl.py b/leafmap/maplibregl.py index 99541da933..a666193d7a 100644 --- a/leafmap/maplibregl.py +++ b/leafmap/maplibregl.py @@ -83,6 +83,9 @@ basemaps = Box(xyz_to_leaflet(), frozen_box=True) +SIDEBAR_PANEL_BACKGROUND = "#ffffff" +SIDEBAR_PANEL_TEXT_COLOR = "#212121" + class Map(MapWidget): """The Map class inherits from the MapWidget class of the maplibre.ipywidget module.""" @@ -5455,19 +5458,30 @@ def add_floating_sidebar( toggle_icon = v.Icon( children=["mdi-chevron-left"] if sidebar_visible else ["mdi-chevron-right"], small=True, + style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", ) toggle_btn = v.Btn( icon=True, children=[toggle_icon], - style_="width: 22px; height: 22px; min-width: 22px; padding: 0;", + style_=( + "width: 22px; height: 22px; min-width: 22px; padding: 0; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), ) # Create settings/wrench button - settings_icon = v.Icon(children=["mdi-wrench"], small=True) + settings_icon = v.Icon( + children=["mdi-wrench"], + small=True, + style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + ) settings_btn = v.Btn( icon=True, children=[settings_icon], - style_="width: 22px; height: 22px; min-width: 22px; padding: 0;", + style_=( + "width: 22px; height: 22px; min-width: 22px; padding: 0; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), ) # Create header row with toggle and settings buttons @@ -8172,21 +8186,30 @@ def __init__( # Toggle button self.toggle_icon = v.Icon( - children=["mdi-chevron-right"] if sidebar_visible else ["mdi-chevron-left"] + children=["mdi-chevron-right"] if sidebar_visible else ["mdi-chevron-left"], + style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", ) self.toggle_btn = v.Btn( icon=True, children=[self.toggle_icon], - style_="width: 48px; height: 48px; min-width: 48px;", + style_=( + "width: 48px; height: 48px; min-width: 48px; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), ) self.toggle_btn.on_event("click", self.toggle_sidebar) # Settings icon - self.settings_icon = v.Icon(children=["mdi-wrench"]) + self.settings_icon = v.Icon( + children=["mdi-wrench"], + style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + ) self.settings_btn = v.Btn( icon=True, children=[self.settings_icon], - style_="width: 36px; height: 36px;", + style_=( + "width: 36px; height: 36px; " f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), ) self.settings_btn.on_event("click", self.toggle_width_slider) @@ -10317,19 +10340,32 @@ def __init__( icon=True, small=True, class_="ma-0", - style_="min-width: 24px; width: 24px;", - children=[v.Icon(children=[close_icon])], + style_=f"min-width: 24px; width: 24px; color: {SIDEBAR_PANEL_TEXT_COLOR};", + children=[ + v.Icon( + children=[close_icon], + style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + ) + ], ) close_btn.on_event("click", self._handle_close) header = v.ExpansionPanelHeader( - style_=f"height: {height}; min-height: {height}; background-color: {background_color};", + style_=( + f"height: {height}; min-height: {height}; " + f"background-color: {background_color}; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), children=[ v.Row( align="center", class_="d-flex flex-grow-1 align-center", children=[ - v.Icon(children=[layer_icon], class_="ml-1"), + v.Icon( + children=[layer_icon], + class_="ml-1", + style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + ), v.Spacer(), # push title to center v.Html(tag="span", children=[label], class_="text-subtitle-2"), v.Spacer(), # push close to right @@ -10341,16 +10377,24 @@ def __init__( ) panel = v.ExpansionPanel( + style_=( + f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), children=[ header, v.ExpansionPanelContent( + style_=( + f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), children=[ widgets.VBox( [self.master_toggle, self.group_toggles, self.layers_box] ) - ] + ], ), - ] + ], ) if expanded: @@ -10591,19 +10635,32 @@ def __init__( icon=True, small=True, class_="ma-0", - style_="min-width: 24px; width: 24px;", - children=[v.Icon(children=[close_icon])], + style_=f"min-width: 24px; width: 24px; color: {SIDEBAR_PANEL_TEXT_COLOR};", + children=[ + v.Icon( + children=[close_icon], + style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + ) + ], ) close_btn.on_event("click", self._handle_close) header = v.ExpansionPanelHeader( - style_=f"height: {height}; min-height: {height}; background-color: {background_color};", + style_=( + f"height: {height}; min-height: {height}; " + f"background-color: {background_color}; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), children=[ v.Row( align="center", class_="d-flex flex-grow-1 align-center", children=[ - v.Icon(children=[widget_icon], class_="ml-1"), + v.Icon( + children=[widget_icon], + class_="ml-1", + style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + ), v.Spacer(), # push title to center v.Html(tag="span", children=[label], class_="text-subtitle-2"), v.Spacer(), # push close to right @@ -10615,10 +10672,20 @@ def __init__( ) self.panel = v.ExpansionPanel( + style_=( + f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), children=[ header, - v.ExpansionPanelContent(children=[self.content_box]), - ] + v.ExpansionPanelContent( + style_=( + f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " + f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + ), + children=[self.content_box], + ), + ], ) super().__init__( From 1242614649c683852edf8cda1ca9973a123aabfc Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 29 Apr 2026 23:26:42 -0400 Subject: [PATCH 2/3] Enhance sidebar layout and scrollbar styles --- leafmap/maplibregl.py | 53 +++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/leafmap/maplibregl.py b/leafmap/maplibregl.py index a666193d7a..a8d9fa56ce 100644 --- a/leafmap/maplibregl.py +++ b/leafmap/maplibregl.py @@ -85,6 +85,7 @@ SIDEBAR_PANEL_BACKGROUND = "#ffffff" SIDEBAR_PANEL_TEXT_COLOR = "#212121" +SIDEBAR_SCROLLBAR_STYLE = "scrollbar-color: #bdbdbd #f5f5f5; scrollbar-width: thin;" class Map(MapWidget): @@ -556,7 +557,9 @@ def add_to_sidebar( and self.add_floating_sidebar_flag and not hasattr(self, "floating_sidebar_content_box") ): - self.floating_sidebar_content_box = widgets.VBox(children=[]) + self.floating_sidebar_content_box = widgets.VBox( + children=[], layout=widgets.Layout(width="100%", overflow="hidden") + ) self._floating_sidebar_widgets = {} # Check if floating sidebar is being used @@ -5441,7 +5444,9 @@ def add_floating_sidebar( # Initialize floating sidebar state if not hasattr(self, "floating_sidebar_content_box"): - self.floating_sidebar_content_box = widgets.VBox(children=[]) + self.floating_sidebar_content_box = widgets.VBox( + children=[], layout=widgets.Layout(width="100%", overflow="hidden") + ) if not hasattr(self, "_floating_sidebar_widgets"): self._floating_sidebar_widgets = {} @@ -5452,7 +5457,10 @@ def add_floating_sidebar( content_widgets.extend(sidebar_content) # Create main sidebar box with layer manager and additional content - main_sidebar_box = widgets.VBox(children=content_widgets) + main_sidebar_box = widgets.VBox( + children=content_widgets, + layout=widgets.Layout(width="100%", overflow="hidden"), + ) # Create toggle button toggle_icon = v.Icon( @@ -5517,7 +5525,7 @@ class SidebarState: step=10, description="Width:", continuous_update=True, - layout=widgets.Layout(width="100%"), + layout=widgets.Layout(width="100%", min_width="0"), ) # Width change handler @@ -5534,6 +5542,7 @@ def on_width_change(change): z-index: 1000; background-color: white; border-radius: 4px; + {SIDEBAR_SCROLLBAR_STYLE} """ width_slider.observe(on_width_change, names="value") @@ -5568,7 +5577,8 @@ def on_settings_click(widget, event, data): # Combine main sidebar with dynamic content box for layer settings and other widgets sidebar_box = widgets.VBox( - children=[main_sidebar_box, self.floating_sidebar_content_box] + children=[main_sidebar_box, self.floating_sidebar_content_box], + layout=widgets.Layout(width="100%", overflow="hidden"), ) # Toggle function @@ -5593,6 +5603,7 @@ def toggle_sidebar(widget, event, data): z-index: 1000; background-color: white; border-radius: 4px; + {SIDEBAR_SCROLLBAR_STYLE} """ else: # Hide sidebar content, only show toggle button @@ -5609,6 +5620,7 @@ def toggle_sidebar(widget, event, data): background-color: white; border-radius: 4px; padding: 4px; + {SIDEBAR_SCROLLBAR_STYLE} """ toggle_btn.on_event("click", toggle_sidebar) @@ -5628,6 +5640,7 @@ def toggle_sidebar(widget, event, data): z-index: 1000; background-color: white; border-radius: 4px; + {SIDEBAR_SCROLLBAR_STYLE} """ else: initial_style = f""" @@ -5641,6 +5654,7 @@ def toggle_sidebar(widget, event, data): background-color: white; border-radius: 4px; padding: 4px; + {SIDEBAR_SCROLLBAR_STYLE} """ overlay = v.Card( @@ -8180,7 +8194,9 @@ def __init__( self.map_stack.children = [m] # Sidebar content container - self.sidebar_content_box = widgets.VBox() + self.sidebar_content_box = widgets.VBox( + layout=widgets.Layout(width="100%", overflow="hidden") + ) if sidebar_content: self.set_sidebar_content(sidebar_content) @@ -10379,14 +10395,16 @@ def __init__( panel = v.ExpansionPanel( style_=( f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + f"color: {SIDEBAR_PANEL_TEXT_COLOR}; " + f"overflow-x: hidden; {SIDEBAR_SCROLLBAR_STYLE}" ), children=[ header, v.ExpansionPanelContent( style_=( f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + f"color: {SIDEBAR_PANEL_TEXT_COLOR}; " + f"overflow-x: hidden; {SIDEBAR_SCROLLBAR_STYLE}" ), children=[ widgets.VBox( @@ -10436,8 +10454,8 @@ def build_layer_controls(self) -> None: checkbox = widgets.Checkbox(value=visible, description=name, style=style) checkbox.layout.flex = "1 1 auto" - checkbox.layout.max_width = "200px" - checkbox.layout.min_width = "120px" + checkbox.layout.min_width = "0" + checkbox.layout.overflow = "hidden" slider = widgets.FloatSlider( value=opacity, @@ -10447,7 +10465,10 @@ def build_layer_controls(self) -> None: readout=False, tooltip="Change layer opacity", layout=widgets.Layout( - flex="1 1 auto", min_width="120px", padding=padding + flex="0 0 150px", + width="150px", + min_width="100px", + padding=padding, ), ) @@ -10622,7 +10643,9 @@ def __init__( **kwargs (Any): Additional keyword arguments for the parent class. """ # Wrap content in a mutable VBox - self.content_box = widgets.VBox() + self.content_box = widgets.VBox( + layout=widgets.Layout(width="100%", overflow="hidden") + ) self.host_map = host_map if widget: if isinstance(widget, (list, tuple)): @@ -10674,14 +10697,16 @@ def __init__( self.panel = v.ExpansionPanel( style_=( f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + f"color: {SIDEBAR_PANEL_TEXT_COLOR}; " + f"overflow-x: hidden; {SIDEBAR_SCROLLBAR_STYLE}" ), children=[ header, v.ExpansionPanelContent( style_=( f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + f"color: {SIDEBAR_PANEL_TEXT_COLOR}; " + f"overflow-x: hidden; {SIDEBAR_SCROLLBAR_STYLE}" ), children=[self.content_box], ), From c2d07fcff3e1a6b26a4677b82ad1ddde8d8783e9 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 29 Apr 2026 23:30:58 -0400 Subject: [PATCH 3/3] Address Copilot review feedback - Apply SIDEBAR_PANEL_BACKGROUND and SIDEBAR_PANEL_TEXT_COLOR to the floating sidebar v.Card overlay so the panel background respects the centralized constants instead of hardcoded white. - Add a text_color parameter to LayerManagerWidget so callers customizing the header background can keep header contrast; use it consistently for icons, close button, and panel content. - Add a text_color parameter to CustomWidget for the same reason; default still matches SIDEBAR_PANEL_TEXT_COLOR. - Convert implicit string-literal concatenation in Container/Map sidebar button style_ values to single f-strings for readability. --- leafmap/maplibregl.py | 57 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/leafmap/maplibregl.py b/leafmap/maplibregl.py index a8d9fa56ce..b358c8a346 100644 --- a/leafmap/maplibregl.py +++ b/leafmap/maplibregl.py @@ -5472,7 +5472,7 @@ def add_floating_sidebar( icon=True, children=[toggle_icon], style_=( - "width: 22px; height: 22px; min-width: 22px; padding: 0; " + f"width: 22px; height: 22px; min-width: 22px; padding: 0; " f"color: {SIDEBAR_PANEL_TEXT_COLOR};" ), ) @@ -5487,7 +5487,7 @@ def add_floating_sidebar( icon=True, children=[settings_icon], style_=( - "width: 22px; height: 22px; min-width: 22px; padding: 0; " + f"width: 22px; height: 22px; min-width: 22px; padding: 0; " f"color: {SIDEBAR_PANEL_TEXT_COLOR};" ), ) @@ -5540,7 +5540,8 @@ def on_width_change(change): overflow-y: auto; overflow-x: hidden; z-index: 1000; - background-color: white; + background-color: {SIDEBAR_PANEL_BACKGROUND}; + color: {SIDEBAR_PANEL_TEXT_COLOR}; border-radius: 4px; {SIDEBAR_SCROLLBAR_STYLE} """ @@ -5601,7 +5602,8 @@ def toggle_sidebar(widget, event, data): overflow-y: auto; overflow-x: hidden; z-index: 1000; - background-color: white; + background-color: {SIDEBAR_PANEL_BACKGROUND}; + color: {SIDEBAR_PANEL_TEXT_COLOR}; border-radius: 4px; {SIDEBAR_SCROLLBAR_STYLE} """ @@ -5617,7 +5619,8 @@ def toggle_sidebar(widget, event, data): max-height: none; overflow: visible; z-index: 1000; - background-color: white; + background-color: {SIDEBAR_PANEL_BACKGROUND}; + color: {SIDEBAR_PANEL_TEXT_COLOR}; border-radius: 4px; padding: 4px; {SIDEBAR_SCROLLBAR_STYLE} @@ -5638,7 +5641,8 @@ def toggle_sidebar(widget, event, data): overflow-y: auto; overflow-x: hidden; z-index: 1000; - background-color: white; + background-color: {SIDEBAR_PANEL_BACKGROUND}; + color: {SIDEBAR_PANEL_TEXT_COLOR}; border-radius: 4px; {SIDEBAR_SCROLLBAR_STYLE} """ @@ -5651,7 +5655,8 @@ def toggle_sidebar(widget, event, data): max-height: none; overflow: visible; z-index: 1000; - background-color: white; + background-color: {SIDEBAR_PANEL_BACKGROUND}; + color: {SIDEBAR_PANEL_TEXT_COLOR}; border-radius: 4px; padding: 4px; {SIDEBAR_SCROLLBAR_STYLE} @@ -8209,7 +8214,7 @@ def __init__( icon=True, children=[self.toggle_icon], style_=( - "width: 48px; height: 48px; min-width: 48px; " + f"width: 48px; height: 48px; min-width: 48px; " f"color: {SIDEBAR_PANEL_TEXT_COLOR};" ), ) @@ -8223,9 +8228,7 @@ def __init__( self.settings_btn = v.Btn( icon=True, children=[self.settings_icon], - style_=( - "width: 36px; height: 36px; " f"color: {SIDEBAR_PANEL_TEXT_COLOR};" - ), + style_=f"width: 36px; height: 36px; color: {SIDEBAR_PANEL_TEXT_COLOR};", ) self.settings_btn.on_event("click", self.toggle_width_slider) @@ -10303,6 +10306,7 @@ def __init__( close_icon: str = "mdi-close", label="Layers", background_color: str = "#f5f5f5", + text_color: str = SIDEBAR_PANEL_TEXT_COLOR, groups: dict = None, *args: Any, **kwargs: Any, @@ -10318,6 +10322,9 @@ def __init__( close_icon (str): The icon for the close button. Defaults to "mdi-close". label (str): The label for the layer manager. Defaults to "Layers". background_color (str): The background color of the header. Defaults to "#f5f5f5". + text_color (str): The color used for header text and icons. Defaults to + ``SIDEBAR_PANEL_TEXT_COLOR``. Override this when passing a darker + ``background_color`` so the header stays readable. groups (dict): A dictionary of layer groups, such as {"Group 1": ["layer1", "layer2"], "Group 2": ["layer3", "layer4"]}. A group layer toggle will be created for each group. Defaults to None. @@ -10356,11 +10363,11 @@ def __init__( icon=True, small=True, class_="ma-0", - style_=f"min-width: 24px; width: 24px; color: {SIDEBAR_PANEL_TEXT_COLOR};", + style_=f"min-width: 24px; width: 24px; color: {text_color};", children=[ v.Icon( children=[close_icon], - style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + style_=f"color: {text_color};", ) ], ) @@ -10370,7 +10377,7 @@ def __init__( style_=( f"height: {height}; min-height: {height}; " f"background-color: {background_color}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + f"color: {text_color};" ), children=[ v.Row( @@ -10380,7 +10387,7 @@ def __init__( v.Icon( children=[layer_icon], class_="ml-1", - style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + style_=f"color: {text_color};", ), v.Spacer(), # push title to center v.Html(tag="span", children=[label], class_="text-subtitle-2"), @@ -10395,7 +10402,7 @@ def __init__( panel = v.ExpansionPanel( style_=( f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR}; " + f"color: {text_color}; " f"overflow-x: hidden; {SIDEBAR_SCROLLBAR_STYLE}" ), children=[ @@ -10403,7 +10410,7 @@ def __init__( v.ExpansionPanelContent( style_=( f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR}; " + f"color: {text_color}; " f"overflow-x: hidden; {SIDEBAR_SCROLLBAR_STYLE}" ), children=[ @@ -10622,6 +10629,7 @@ def __init__( close_icon: str = "mdi-close", label: str = "My Tools", background_color: str = "#f5f5f5", + text_color: str = SIDEBAR_PANEL_TEXT_COLOR, height: str = "40px", expanded: bool = True, host_map: Optional[Any] = None, @@ -10636,6 +10644,9 @@ def __init__( widget_icon (str): Icon for the header. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons. close_icon (str): Icon for the close button. See https://pictogrammers.github.io/@mdi/font/2.0.46/ for available icons. background_color (str): Background color of the header. Defaults to "#f5f5f5". + text_color (str): Color used for header text and icons. Defaults to + ``SIDEBAR_PANEL_TEXT_COLOR``. Override this when passing a darker + ``background_color`` so the header stays readable. label (str): Text label for the header. Defaults to "My Tools". height (str): Height of the header. Defaults to "40px". expanded (bool): Whether the panel is expanded by default. Defaults to True. @@ -10658,11 +10669,11 @@ def __init__( icon=True, small=True, class_="ma-0", - style_=f"min-width: 24px; width: 24px; color: {SIDEBAR_PANEL_TEXT_COLOR};", + style_=f"min-width: 24px; width: 24px; color: {text_color};", children=[ v.Icon( children=[close_icon], - style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + style_=f"color: {text_color};", ) ], ) @@ -10672,7 +10683,7 @@ def __init__( style_=( f"height: {height}; min-height: {height}; " f"background-color: {background_color}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR};" + f"color: {text_color};" ), children=[ v.Row( @@ -10682,7 +10693,7 @@ def __init__( v.Icon( children=[widget_icon], class_="ml-1", - style_=f"color: {SIDEBAR_PANEL_TEXT_COLOR};", + style_=f"color: {text_color};", ), v.Spacer(), # push title to center v.Html(tag="span", children=[label], class_="text-subtitle-2"), @@ -10697,7 +10708,7 @@ def __init__( self.panel = v.ExpansionPanel( style_=( f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR}; " + f"color: {text_color}; " f"overflow-x: hidden; {SIDEBAR_SCROLLBAR_STYLE}" ), children=[ @@ -10705,7 +10716,7 @@ def __init__( v.ExpansionPanelContent( style_=( f"background-color: {SIDEBAR_PANEL_BACKGROUND}; " - f"color: {SIDEBAR_PANEL_TEXT_COLOR}; " + f"color: {text_color}; " f"overflow-x: hidden; {SIDEBAR_SCROLLBAR_STYLE}" ), children=[self.content_box],