aboutsummaryrefslogtreecommitdiff
path: root/app.py
diff options
context:
space:
mode:
authorTyler <tyler@tylerhoang.xyz>2026-05-17 01:32:09 -0700
committerTyler <tyler@tylerhoang.xyz>2026-05-17 01:32:09 -0700
commit676ef596d16f39f5e101ea50ef025dd5415705ce (patch)
tree721ffaada7e93b4b090b5cc914bb7f086e4b2547 /app.py
parent039d09f5cfb8862c6601501f5d80e87f5d5eb7b5 (diff)
Add TopBar: search hint, NYSE clock, account chip; fix sticky
Replaces the keyed-container sticky hack with a proper psm-top HTML bar (st.markdown) matching the design system. Clock is updated via components.html writing to window.parent.document. Fixes ancestor overflow so position:sticky works. Moves the / shortcut listener to window.parent.document so it fires regardless of iframe focus. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'app.py')
-rw-r--r--app.py104
1 files changed, 90 insertions, 14 deletions
diff --git a/app.py b/app.py
index 87a6716..2ef27d6 100644
--- a/app.py
+++ b/app.py
@@ -220,14 +220,53 @@ button[kind="secondary"]:hover {
padding-bottom: 3rem !important;
}
-/* ── Sticky market bar ──────────────────────────────────────────────────── */
-.st-key-market_bar_sticky {
- position: sticky !important;
- top: 0 !important;
- z-index: 200 !important;
- background: var(--ink-0) !important;
- padding-bottom: 0.5rem !important;
- margin-bottom: -0.5rem !important;
+/* ── Allow sticky positioning inside Streamlit's main column ────────────── */
+section[data-testid="stMain"] { overflow: visible !important; }
+
+/* ── TopBar ─────────────────────────────────────────────────────────────── */
+.psm-top {
+ position: sticky; top: 0; z-index: 200;
+ background: var(--ink-0); border-bottom: 1px solid var(--line-1);
+ padding: 10px 0; margin-bottom: 0.75rem;
+ display: flex; align-items: center; gap: 16px;
+}
+.psm-search-bar {
+ flex: 1; max-width: 460px;
+ display: flex; align-items: center; gap: 8px;
+ background: var(--ink-2); border: 1px solid var(--line-2);
+ border-radius: var(--r-2); padding: 7px 12px;
+}
+.psm-search-bar input {
+ flex: 1; background: transparent; border: none; outline: none;
+ font-family: var(--font-mono); font-size: var(--fs-13); color: var(--fg-1);
+ pointer-events: none;
+}
+.psm-search-bar input::placeholder { color: var(--fg-3); }
+.psm-search-bar .kbd {
+ font-family: var(--font-mono); font-size: var(--fs-12); color: var(--fg-3);
+ border: 1px solid var(--line-2); padding: 1px 6px;
+ border-radius: var(--r-1); background: var(--ink-1); flex-shrink: 0;
+}
+.psm-topbar-clock {
+ font-family: var(--font-mono); font-size: var(--fs-12); color: var(--fg-2);
+ display: flex; gap: 10px; align-items: center;
+}
+.psm-topbar-dot {
+ width: 6px; height: 6px; border-radius: 50%;
+ background: var(--fg-4); flex-shrink: 0;
+}
+.psm-topbar-dot.open { background: var(--positive); box-shadow: 0 0 5px var(--positive); }
+.psm-topbar-account {
+ display: flex; align-items: center; gap: 8px;
+ padding: 5px 12px; border: 1px solid var(--line-2); border-radius: var(--r-full);
+ font-family: var(--font-sans); font-size: var(--fs-13); color: var(--fg-1);
+ margin-left: auto;
+}
+.psm-topbar-av {
+ width: 20px; height: 20px; border-radius: 50%;
+ background: var(--brass); color: var(--brass-ink);
+ display: flex; align-items: center; justify-content: center;
+ font-family: var(--font-display); font-style: italic; font-size: 11px;
}
/* ── Tabs ───────────────────────────────────────────────────────────────── */
@@ -784,20 +823,57 @@ with st.sidebar:
render_top_movers(True)
+# ── TopBar ────────────────────────────────────────────────────────────────────
+
+st.markdown(
+ "<div class='psm-top'>"
+ "<div class='psm-search-bar'>"
+ "<input type='text' placeholder='Search ticker, company, or filing…' tabindex='-1' />"
+ "<span class='kbd'>/</span>"
+ "</div>"
+ "<div class='psm-topbar-clock'>"
+ "<span class='psm-topbar-dot' id='psm-dot'></span>"
+ "<span id='psm-status'>NYSE · Closed</span>"
+ "<span id='psm-time'>--:--:-- ET</span>"
+ "</div>"
+ "<div class='psm-topbar-account'>"
+ "<span class='psm-topbar-av'>T</span>"
+ "<span>Tyler Hoang</span>"
+ "</div>"
+ "</div>",
+ unsafe_allow_html=True,
+)
+
# ── Market Bar ────────────────────────────────────────────────────────────────
-with st.container(key="market_bar_sticky"):
+with st.container():
render_market_bar()
st.divider()
-# ── ⌘K / Ctrl+K shortcut — focuses sidebar ticker search ─────────────────────
+# ── TopBar clock + / shortcut ─────────────────────────────────────────────────
components.html(
"<script>"
- "document.addEventListener('keydown', function(e) {"
- " if (e.key === '/' && !e.metaKey && !e.ctrlKey && !e.altKey"
- " && document.activeElement.tagName !== 'INPUT'"
- " && document.activeElement.tagName !== 'TEXTAREA') {"
+ "function tickTopbar() {"
+ " var now = new Date();"
+ " var et = new Date(now.toLocaleString('en-US', { timeZone: 'America/New_York' }));"
+ " var hm = et.getHours() * 60 + et.getMinutes();"
+ " var day = et.getDay();"
+ " var isOpen = (day >= 1 && day <= 5) && hm >= 570 && hm < 960;"
+ " var ts = et.toLocaleTimeString('en-US', { hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false });"
+ " var pd = window.parent.document;"
+ " var dot = pd.getElementById('psm-dot');"
+ " var status = pd.getElementById('psm-status');"
+ " var time = pd.getElementById('psm-time');"
+ " if (dot) dot.className = isOpen ? 'psm-topbar-dot open' : 'psm-topbar-dot';"
+ " if (status) status.textContent = isOpen ? 'NYSE · Open' : 'NYSE · Closed';"
+ " if (time) time.textContent = ts + ' ET';"
+ "}"
+ "tickTopbar(); setInterval(tickTopbar, 1000);"
+ "window.parent.document.addEventListener('keydown', function(e) {"
+ " if (e.key === '/' && !e.metaKey && !e.ctrlKey && !e.altKey) {"
+ " var ae = window.parent.document.activeElement;"
+ " if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA')) return;"
" var inputs = window.parent.document.querySelectorAll('input');"
" for (var i = 0; i < inputs.length; i++) {"
" if (inputs[i].placeholder && inputs[i].placeholder.indexOf('AAPL') > -1) {"