|
1 | 1 | """Module providing a tabs widget for the numerous library.""" |
2 | 2 |
|
| 3 | +import re |
| 4 | +from typing import Any, NamedTuple |
| 5 | + |
3 | 6 | import anywidget |
4 | 7 | import traitlets |
5 | 8 |
|
@@ -56,3 +59,81 @@ def __init__( |
56 | 59 | def selected_value(self) -> str: |
57 | 60 | """Returns the currently selected tab.""" |
58 | 61 | return str(self.active_tab) |
| 62 | + |
| 63 | + |
| 64 | +class TabsWithVisibility(NamedTuple): |
| 65 | + """Container for a Tabs widget and its visibility controllers.""" |
| 66 | + |
| 67 | + tabs: Tabs |
| 68 | + visibility: list[bool] |
| 69 | + |
| 70 | + |
| 71 | +def _sanitize_id(name: str) -> str: |
| 72 | + """ |
| 73 | + Sanitize a name for use in widget IDs. |
| 74 | +
|
| 75 | + Args: |
| 76 | + name: The name to sanitize |
| 77 | +
|
| 78 | + Returns: |
| 79 | + Sanitized name: lowercase, spaces to underscore, \ |
| 80 | + only alphanumeric and underscore |
| 81 | +
|
| 82 | + """ |
| 83 | + # Convert to lowercase |
| 84 | + name = name.lower() |
| 85 | + # Replace spaces and hyphens with underscore |
| 86 | + name = re.sub(r"[\s-]+", "_", name) |
| 87 | + # Remove any characters that aren't alphanumeric or underscore |
| 88 | + return re.sub(r"[^\w]", "", name) |
| 89 | + |
| 90 | + |
| 91 | +def create_tabs_with_visibility( |
| 92 | + tabs: list[str], |
| 93 | + widget_id: str, |
| 94 | + label: str = "", |
| 95 | + tooltip: str | None = None, |
| 96 | + default: str | None = None, |
| 97 | +) -> dict[str, Tabs]: |
| 98 | + r""" |
| 99 | + Create a Tabs widget with associated visibility booleans. |
| 100 | +
|
| 101 | + Args: |
| 102 | + tabs: List of tab names |
| 103 | + widget_id: Unique identifier for this tab group |
| 104 | + label: Label for the tabs widget |
| 105 | + tooltip: Optional tooltip text |
| 106 | + default: Default active tab (defaults to first tab if None) |
| 107 | +
|
| 108 | + Returns: |
| 109 | + Dictionary containing the tabs widget and |
| 110 | + \visibility booleans keyed by widget_id: |
| 111 | + - {widget_id}: Tabs widget |
| 112 | + - {widget_id}_{tab_name}: bool indicating if tab should be hidden |
| 113 | +
|
| 114 | + """ |
| 115 | + # Sanitize widget_id |
| 116 | + widget_id = _sanitize_id(widget_id) |
| 117 | + |
| 118 | + # Create the tabs widget |
| 119 | + tabs_widget = Tabs(tabs=tabs, label=label, tooltip=tooltip, default=default) |
| 120 | + |
| 121 | + # Create result dictionary with tabs widget |
| 122 | + result = {widget_id: tabs_widget} |
| 123 | + |
| 124 | + # Create visibility booleans for each tab |
| 125 | + visibility_states = [tab != tabs_widget.active_tab for tab in tabs_widget.tabs] |
| 126 | + |
| 127 | + # Add visibility states to result with sanitized widget_id keys |
| 128 | + for tab, hidden in zip(tabs_widget.tabs, visibility_states, strict=False): |
| 129 | + result[f"{widget_id}_{_sanitize_id(tab)}"] = hidden |
| 130 | + |
| 131 | + # Set up visibility changes on tab changes |
| 132 | + def on_tab_change(event: Any) -> None: # noqa: ANN401 |
| 133 | + for tab in tabs_widget.tabs: |
| 134 | + key = f"{widget_id}_{_sanitize_id(tab)}" |
| 135 | + result[key] = tab != event.new |
| 136 | + |
| 137 | + tabs_widget.observe(on_tab_change, names="active_tab") |
| 138 | + |
| 139 | + return result |
0 commit comments