diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js
index c00574ed..2c1d5d29 100644
--- a/android/app/src/main/assets/public/main.js
+++ b/android/app/src/main/assets/public/main.js
@@ -4364,19 +4364,41 @@ async function switchSettingsTab(tabName) {
}
async function populateLanguageDropdown() {
- let sel = document.getElementById("settings-language");
- if (!sel) return;
+ let container = document.getElementById("settings-language");
+ let optionsContainer = document.getElementById("settings-language-options");
+ let display = document.getElementById("settings-language-display");
+ if (!container || !optionsContainer || !display) return;
try {
let path = window.location.pathname.replace("/login/index.html", "/").replace("index.html", "");
let res = await fetchAsync(`${path}blah/index.json`);
let json = JSON.parse(res);
- sel.innerHTML = "";
+ optionsContainer.innerHTML = "";
+
+ window._langDropdownListener = window._langDropdownListener || ((e) => {
+ let cont = document.getElementById("settings-language");
+ if (cont && !cont.contains(e.target)) {
+ cont.classList.remove('open');
+ }
+ });
+ document.removeEventListener('click', window._langDropdownListener);
+ document.addEventListener('click', window._langDropdownListener);
+
for (let code in json) {
- let opt = document.createElement("option");
- opt.value = code;
- opt.textContent = json[code].native + " (" + json[code].english + ")";
- if (code === lang) opt.selected = true;
- sel.appendChild(opt);
+ let opt = document.createElement("div");
+ opt.className = "custom-select-option";
+ let text = json[code].native + " (" + json[code].english + ")";
+ opt.textContent = text;
+ if (code === lang) {
+ opt.classList.add("selected");
+ display.textContent = text;
+ display.classList.remove("blah");
+ }
+ opt.onclick = () => {
+ display.textContent = text;
+ container.classList.remove("open");
+ changeLanguage(code);
+ };
+ optionsContainer.appendChild(opt);
}
} catch (e) {
console.error("Failed to load languages", e);
diff --git a/android/app/src/main/assets/public/screens.js b/android/app/src/main/assets/public/screens.js
index d0b90b22..bdbcad05 100644
--- a/android/app/src/main/assets/public/screens.js
+++ b/android/app/src/main/assets/public/screens.js
@@ -415,10 +415,15 @@ var appearanceSettings = `
title.appearance
`;
diff --git a/android/app/src/main/assets/public/style.css b/android/app/src/main/assets/public/style.css
index af2ff9c7..723ecf4b 100644
--- a/android/app/src/main/assets/public/style.css
+++ b/android/app/src/main/assets/public/style.css
@@ -1181,3 +1181,98 @@ input:checked + .toggle-slider:before {
0% { transform: scale(0); }
100% { transform: scale(1); }
}
+
+/* Custom Select */
+.custom-select {
+ position: relative;
+ border-radius: 0.8rem;
+ background-color: rgba(255, 255, 255, 0.05);
+ margin: var(--button-margin);
+ height: var(--button-height);
+ display: flex;
+ cursor: pointer;
+ align-items: center;
+ border: var(--border-width) solid var(--light-border-color);
+ color: var(--text-color);
+ width: calc(100% - (var(--button-margin) * 2));
+ outline: none;
+ user-select: none;
+ z-index: 2; /* to appear above other elements if necessary */
+}
+
+.custom-select-value {
+ padding: var(--button-margin);
+ padding-left: calc(var(--button-margin) * 2);
+ padding-right: 1.5rem;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.custom-select-value span {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.custom-select .chevron {
+ transition: transform 0.2s ease;
+}
+
+.custom-select.open .chevron {
+ transform: rotate(180deg);
+}
+
+.custom-select-options {
+ position: absolute;
+ top: calc(100% + 0.4rem);
+ left: 0;
+ width: 100%;
+ background-color: var(--main-bg-color);
+ border: var(--border-width) solid var(--light-border-color);
+ border-radius: 0.8rem;
+ max-height: 15rem;
+ overflow-y: auto;
+ z-index: 100;
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(-10px);
+ transition: all 0.2s ease;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
+ padding: 0.4rem;
+ pointer-events: none;
+}
+
+.custom-select.open .custom-select-options {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+ pointer-events: auto;
+}
+
+.custom-select-option {
+ padding: var(--button-margin) calc(var(--button-margin) * 2);
+ height: var(--button-height);
+ display: flex;
+ align-items: center;
+ border-radius: 0.8rem;
+ background-color: transparent;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ text-align: left;
+ margin-bottom: 0.2rem;
+}
+
+.custom-select-option:last-child {
+ margin-bottom: 0;
+}
+
+.custom-select-option:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.custom-select-option.selected {
+ background-color: rgba(255, 255, 255, 0.15);
+ font-weight: bold;
+}
diff --git a/webroot/main.js b/webroot/main.js
index c00574ed..2c1d5d29 100644
--- a/webroot/main.js
+++ b/webroot/main.js
@@ -4364,19 +4364,41 @@ async function switchSettingsTab(tabName) {
}
async function populateLanguageDropdown() {
- let sel = document.getElementById("settings-language");
- if (!sel) return;
+ let container = document.getElementById("settings-language");
+ let optionsContainer = document.getElementById("settings-language-options");
+ let display = document.getElementById("settings-language-display");
+ if (!container || !optionsContainer || !display) return;
try {
let path = window.location.pathname.replace("/login/index.html", "/").replace("index.html", "");
let res = await fetchAsync(`${path}blah/index.json`);
let json = JSON.parse(res);
- sel.innerHTML = "";
+ optionsContainer.innerHTML = "";
+
+ window._langDropdownListener = window._langDropdownListener || ((e) => {
+ let cont = document.getElementById("settings-language");
+ if (cont && !cont.contains(e.target)) {
+ cont.classList.remove('open');
+ }
+ });
+ document.removeEventListener('click', window._langDropdownListener);
+ document.addEventListener('click', window._langDropdownListener);
+
for (let code in json) {
- let opt = document.createElement("option");
- opt.value = code;
- opt.textContent = json[code].native + " (" + json[code].english + ")";
- if (code === lang) opt.selected = true;
- sel.appendChild(opt);
+ let opt = document.createElement("div");
+ opt.className = "custom-select-option";
+ let text = json[code].native + " (" + json[code].english + ")";
+ opt.textContent = text;
+ if (code === lang) {
+ opt.classList.add("selected");
+ display.textContent = text;
+ display.classList.remove("blah");
+ }
+ opt.onclick = () => {
+ display.textContent = text;
+ container.classList.remove("open");
+ changeLanguage(code);
+ };
+ optionsContainer.appendChild(opt);
}
} catch (e) {
console.error("Failed to load languages", e);
diff --git a/webroot/screens.js b/webroot/screens.js
index d0b90b22..bdbcad05 100644
--- a/webroot/screens.js
+++ b/webroot/screens.js
@@ -415,10 +415,15 @@ var appearanceSettings = `
title.appearance
`;
diff --git a/webroot/style.css b/webroot/style.css
index af2ff9c7..723ecf4b 100644
--- a/webroot/style.css
+++ b/webroot/style.css
@@ -1181,3 +1181,98 @@ input:checked + .toggle-slider:before {
0% { transform: scale(0); }
100% { transform: scale(1); }
}
+
+/* Custom Select */
+.custom-select {
+ position: relative;
+ border-radius: 0.8rem;
+ background-color: rgba(255, 255, 255, 0.05);
+ margin: var(--button-margin);
+ height: var(--button-height);
+ display: flex;
+ cursor: pointer;
+ align-items: center;
+ border: var(--border-width) solid var(--light-border-color);
+ color: var(--text-color);
+ width: calc(100% - (var(--button-margin) * 2));
+ outline: none;
+ user-select: none;
+ z-index: 2; /* to appear above other elements if necessary */
+}
+
+.custom-select-value {
+ padding: var(--button-margin);
+ padding-left: calc(var(--button-margin) * 2);
+ padding-right: 1.5rem;
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.custom-select-value span {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.custom-select .chevron {
+ transition: transform 0.2s ease;
+}
+
+.custom-select.open .chevron {
+ transform: rotate(180deg);
+}
+
+.custom-select-options {
+ position: absolute;
+ top: calc(100% + 0.4rem);
+ left: 0;
+ width: 100%;
+ background-color: var(--main-bg-color);
+ border: var(--border-width) solid var(--light-border-color);
+ border-radius: 0.8rem;
+ max-height: 15rem;
+ overflow-y: auto;
+ z-index: 100;
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(-10px);
+ transition: all 0.2s ease;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
+ padding: 0.4rem;
+ pointer-events: none;
+}
+
+.custom-select.open .custom-select-options {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(0);
+ pointer-events: auto;
+}
+
+.custom-select-option {
+ padding: var(--button-margin) calc(var(--button-margin) * 2);
+ height: var(--button-height);
+ display: flex;
+ align-items: center;
+ border-radius: 0.8rem;
+ background-color: transparent;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ text-align: left;
+ margin-bottom: 0.2rem;
+}
+
+.custom-select-option:last-child {
+ margin-bottom: 0;
+}
+
+.custom-select-option:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.custom-select-option.selected {
+ background-color: rgba(255, 255, 255, 0.15);
+ font-weight: bold;
+}