diff --git a/src/anywidget_vector/ui/__init__.py b/src/anywidget_vector/ui/__init__.py index 59590e7..493f1fd 100644 --- a/src/anywidget_vector/ui/__init__.py +++ b/src/anywidget_vector/ui/__init__.py @@ -122,10 +122,18 @@ def get_esm() -> str: function render({{ model, el }}) {{ const wrapper = document.createElement("div"); wrapper.className = "avs-wrapper"; + if (model.get("dark_mode")) wrapper.classList.add("avs-dark"); wrapper.style.width = model.get("width") + "px"; wrapper.style.height = model.get("height") + "px"; el.appendChild(wrapper); + model.on("change:dark_mode", () => {{ + const dark = model.get("dark_mode"); + wrapper.classList.toggle("avs-dark", dark); + model.set("background", dark ? "#1a1a2e" : "#fafafa"); + model.save_changes(); + }}); + let sidebar = null; let settingsPanel = null; let propertiesPanel = null; diff --git a/src/anywidget_vector/ui/settings.js b/src/anywidget_vector/ui/settings.js index 71b1dec..15525aa 100644 --- a/src/anywidget_vector/ui/settings.js +++ b/src/anywidget_vector/ui/settings.js @@ -21,6 +21,31 @@ export function createSettingsPanel(model, callbacks) { header.appendChild(closeBtn); inner.appendChild(header); + // Dark mode toggle + const themeGroup = createFormGroup("Theme"); + const toggle = document.createElement("label"); + toggle.className = "avs-toggle"; + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.checked = model.get("dark_mode"); + checkbox.addEventListener("change", (e) => { + model.set("dark_mode", e.target.checked); + model.save_changes(); + }); + model.on("change:dark_mode", () => { + checkbox.checked = model.get("dark_mode"); + }); + const slider = document.createElement("span"); + slider.className = "avs-toggle-slider"; + const toggleLabel = document.createElement("span"); + toggleLabel.className = "avs-toggle-label"; + toggleLabel.textContent = "Dark mode"; + toggle.appendChild(checkbox); + toggle.appendChild(slider); + toggle.appendChild(toggleLabel); + themeGroup.appendChild(toggle); + inner.appendChild(themeGroup); + // Backend selector const backendGroup = createFormGroup("Backend"); const backendSelect = document.createElement("select"); diff --git a/src/anywidget_vector/ui/styles.css b/src/anywidget_vector/ui/styles.css index 25ce083..efa8a14 100644 --- a/src/anywidget_vector/ui/styles.css +++ b/src/anywidget_vector/ui/styles.css @@ -1,12 +1,14 @@ /* === CSS Variables === */ + +/* Light mode (default) */ .avs-wrapper { - --avs-bg: #1a1a2e; - --avs-surface: #252542; - --avs-border: #3a3a5c; - --avs-text: #e0e0e0; - --avs-text-muted: #888; + --avs-bg: #ffffff; + --avs-surface: #f8f9fa; + --avs-border: #e5e7eb; + --avs-text: #111827; + --avs-text-muted: #6b7280; --avs-primary: #6366f1; - --avs-primary-hover: #818cf8; + --avs-primary-hover: #4f46e5; --avs-success: #10b981; --avs-warning: #f59e0b; --avs-error: #ef4444; @@ -21,6 +23,17 @@ flex-direction: column; } +/* Dark mode */ +.avs-wrapper.avs-dark { + --avs-bg: #1a1a2e; + --avs-surface: #252542; + --avs-border: #3a3a5c; + --avs-text: #e0e0e0; + --avs-text-muted: #888; + --avs-primary: #6366f1; + --avs-primary-hover: #818cf8; +} + /* === Main Layout === */ .avs-main { display: flex; @@ -542,3 +555,49 @@ padding: 10px 16px; } } + +/* === Toggle Switch === */ +.avs-toggle { + display: flex; + align-items: center; + cursor: pointer; + gap: 10px; +} + +.avs-toggle input { + display: none; +} + +.avs-toggle-slider { + width: 36px; + height: 20px; + background: var(--avs-border); + border-radius: 10px; + position: relative; + transition: background 0.2s; +} + +.avs-toggle-slider::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + background: #fff; + border-radius: 50%; + transition: transform 0.2s; +} + +.avs-toggle input:checked + .avs-toggle-slider { + background: var(--avs-primary); +} + +.avs-toggle input:checked + .avs-toggle-slider::after { + transform: translateX(16px); +} + +.avs-toggle-label { + font-size: 13px; + color: var(--avs-text); +} diff --git a/src/anywidget_vector/widget.py b/src/anywidget_vector/widget.py index ddad048..4b33a7e 100644 --- a/src/anywidget_vector/widget.py +++ b/src/anywidget_vector/widget.py @@ -38,6 +38,9 @@ class VectorSpace(anywidget.AnyWidget): height = traitlets.Int(default_value=600).tag(sync=True) background = traitlets.Unicode(default_value="#1a1a2e").tag(sync=True) + # === Theme === + dark_mode = traitlets.Bool(default_value=True).tag(sync=True) + # === Axes and Grid === show_axes = traitlets.Bool(default_value=True).tag(sync=True) show_grid = traitlets.Bool(default_value=True).tag(sync=True)