@enigmax/dashboard 0.1.43 → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/assets/index.html +57 -7
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -354,6 +354,19 @@
354
354
  /* Usage view provider/account pickers. */
355
355
  .useg-pick { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); }
356
356
  .useg-pick select { width: auto; min-width: 170px; }
357
+ /* Icon dropdown: a native <select> can't render icons in its <option>s, so the provider
358
+ picker is a custom listbox - the logo shows on the button AND inside every option. */
359
+ .dd { position: relative; }
360
+ .dd-btn { display: inline-flex; align-items: center; gap: 8px; min-width: 170px; background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 6px 10px; font-size: 12px; font-family: var(--mono); cursor: pointer; }
361
+ .dd-btn:hover { border-color: #2f3744; }
362
+ .dd-btn .dd-label { flex: 1; text-align: left; }
363
+ .dd-btn .dd-caret { width: 13px; height: 13px; flex: 0 0 auto; color: var(--muted); }
364
+ .dd-ic { display: inline-flex; align-items: center; }
365
+ .dd-ic svg { width: 14px; height: 14px; display: block; }
366
+ .dd-menu { position: absolute; top: calc(100% + 4px); left: 0; min-width: 100%; margin: 0; padding: 4px; list-style: none; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: var(--shadow-2); z-index: 50; }
367
+ .dd-opt { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; font-size: 12px; color: var(--text); white-space: nowrap; cursor: pointer; }
368
+ .dd-opt:hover, .dd-opt:focus { background: var(--surface2); outline: none; }
369
+ .dd-opt.sel { color: var(--accent); }
357
370
  /* Icon picker: clickable app cards (a native <select> can't render SVG in options). */
358
371
  .modal-picker { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
359
372
  .pick-card {
@@ -507,7 +520,12 @@
507
520
  </div>
508
521
  </div>
509
522
  <div class="useg-controls" style="display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-bottom:16px">
510
- <label class="useg-pick"><span>Provider</span><span id="uProviderIcon" class="aicon" aria-hidden="true"></span><select id="uProvider" class="modal-input"></select></label>
523
+ <div class="useg-pick"><span>Provider</span>
524
+ <div class="dd" id="uProviderDD">
525
+ <button type="button" class="dd-btn" aria-haspopup="listbox" aria-expanded="false"><span class="dd-ic"></span><span class="dd-label"></span><svg class="dd-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg></button>
526
+ <ul class="dd-menu" role="listbox" aria-label="Provider" hidden></ul>
527
+ </div>
528
+ </div>
511
529
  <label class="useg-pick" id="uAccountWrap"><span>Account</span><select id="uAccount" class="modal-input"></select></label>
512
530
  </div>
513
531
  <div class="panel" id="usagePanel" style="display:none">
@@ -1886,12 +1904,44 @@ export {
1886
1904
  try { await fetch("/api/plan", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key, value: Number(value) }) }); refreshAll(true); }
1887
1905
  catch { /* leave as-is */ }
1888
1906
  }
1907
+ // Accessible icon dropdown (a native <select> can't render icons in its <option>s). The
1908
+ // button shows the selected entry's icon + label; the listbox shows every entry with its
1909
+ // icon. entries: [{ value, label, icon }]; getVal()/onPick(value) bind it to state.
1910
+ function iconDropdown(rootId, entries, getVal, onPick) {
1911
+ const root = $(rootId), btn = root.querySelector(".dd-btn"), menu = root.querySelector(".dd-menu");
1912
+ const btnIc = btn.querySelector(".dd-ic"), btnLabel = btn.querySelector(".dd-label");
1913
+ const opts = () => [...menu.querySelectorAll(".dd-opt")];
1914
+ const render = () => {
1915
+ const cur = getVal(), e = entries.find((x) => x.value === cur) || entries[0];
1916
+ btnIc.innerHTML = e.icon || ""; btnLabel.textContent = e.label;
1917
+ menu.innerHTML = entries.map((x) => '<li class="dd-opt' + (x.value === cur ? " sel" : "")
1918
+ + '" role="option" tabindex="-1" data-val="' + esc(x.value) + '" aria-selected="' + (x.value === cur)
1919
+ + '"><span class="dd-ic">' + (x.icon || "") + "</span><span>" + esc(x.label) + "</span></li>").join("");
1920
+ };
1921
+ const close = () => { menu.hidden = true; btn.setAttribute("aria-expanded", "false"); };
1922
+ const open = () => { menu.hidden = false; btn.setAttribute("aria-expanded", "true"); (menu.querySelector(".sel") || opts()[0]) && (menu.querySelector(".sel") || opts()[0]).focus(); };
1923
+ const pick = (li) => { close(); btn.focus(); if (li && li.dataset.val) onPick(li.dataset.val); };
1924
+ btn.addEventListener("click", (e) => { e.stopPropagation(); menu.hidden ? open() : close(); });
1925
+ btn.addEventListener("keydown", (e) => { if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") { e.preventDefault(); open(); } });
1926
+ menu.addEventListener("click", (e) => pick(e.target.closest(".dd-opt")));
1927
+ menu.addEventListener("keydown", (e) => {
1928
+ const list = opts(), i = list.indexOf(document.activeElement);
1929
+ if (e.key === "Escape") { e.preventDefault(); close(); btn.focus(); }
1930
+ else if (e.key === "ArrowDown") { e.preventDefault(); (list[i + 1] || list[0]).focus(); }
1931
+ else if (e.key === "ArrowUp") { e.preventDefault(); (list[i - 1] || list[list.length - 1]).focus(); }
1932
+ else if (e.key === "Enter" || e.key === " ") { e.preventDefault(); pick(document.activeElement); }
1933
+ });
1934
+ document.addEventListener("click", close);
1935
+ render();
1936
+ return { render };
1937
+ }
1938
+ let providerDD = null;
1889
1939
  function wireUsage() {
1890
- // Provider + account selectors drive a client-side re-render from the cached report.
1891
- const ps = $("uProvider");
1892
- ps.innerHTML = Object.entries(USAGE_PROVIDERS).map(([k, v]) => '<option value="' + k + '">' + esc(v.label) + "</option>").join("");
1893
- ps.value = usageProvider;
1894
- ps.addEventListener("change", () => { usageProvider = ps.value; usageAccount = "__all__"; applyUsageView(); loadProviderStatus(); });
1940
+ // The provider picker is a custom icon dropdown; the account one stays a native select.
1941
+ providerDD = iconDropdown("uProviderDD",
1942
+ Object.entries(USAGE_PROVIDERS).map(([k, v]) => ({ value: k, label: v.label, icon: agentSvg(k) })),
1943
+ () => usageProvider,
1944
+ (val) => { if (val === usageProvider) return; usageProvider = val; usageAccount = "__all__"; applyUsageView(); loadProviderStatus(); });
1895
1945
  $("uAccount").addEventListener("change", () => { usageAccount = $("uAccount").value; applyUsageView(); });
1896
1946
  const el = $("uWindows");
1897
1947
  el.addEventListener("click", (e) => {
@@ -2002,7 +2052,7 @@ export {
2002
2052
  // measurable local store; the others show an honest "no local usage" panel instead.
2003
2053
  function applyUsageView() {
2004
2054
  const p = USAGE_PROVIDERS[usageProvider] || USAGE_PROVIDERS.claude;
2005
- $("uProviderIcon").innerHTML = agentSvg(usageProvider);
2055
+ if (providerDD) providerDD.render();
2006
2056
  $("usageTitle").textContent = p.label + " Usage";
2007
2057
  const link = $("usageViewLink");
2008
2058
  link.href = p.viewHref; link.textContent = p.viewLabel + " ->";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/dashboard",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "Local browser dashboard UI for enigma: the static page and chart assets enigma serves on its loopback dashboard (savings, real tool usage, in-browser settings). Installed on demand by enigma-cli; not a runtime dependency.",
5
5
  "type": "module",
6
6
  "files": [