diff --git a/static/connections.html b/static/connections.html index f3873d3..ea6297e 100644 --- a/static/connections.html +++ b/static/connections.html @@ -66,7 +66,6 @@ } .tree-chevron:hover { color: var(--primary); background: var(--input); } .tree-chevron.expanded { color: var(--primary); } - .tree-chevron.leaf { visibility: hidden; cursor: default; } .tree-children { padding-left: var(--s-4) !important; margin-left: 15px !important; @@ -379,6 +378,131 @@ padding: 1px var(--s-1); font-size: 0.85em; color: var(--text); } .onboard-hl { color: var(--accent); font-weight: bold; } + + /* UX (#254) layout: sidebar scroll, indent, density */ + /* Sidebar is sticky and scrolls on its own so a long folder tree never + drags the entries table off screen; .main keeps scrolling with the page. */ + .sidebar { + position: sticky; top: var(--s-3); + align-self: flex-start; + max-height: calc(100vh - var(--s-3) * 2); + overflow-y: auto; overflow-x: hidden; + overscroll-behavior: contain; + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; + } + .sidebar::-webkit-scrollbar { width: 6px; } + .sidebar::-webkit-scrollbar-track { background: transparent; } + .sidebar::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } + /* Header + tools slot pinned at the top of the sidebar while the list scrolls */ + .sidebar-pin { + position: sticky; top: 0; z-index: 2; + display: flex; flex-direction: column; + background: var(--bg); + } + .sidebar-pin .sidebar-header { margin-bottom: var(--s-2); } + #sidebar-tools:not(:empty) { padding-bottom: var(--s-2); } + + /* Rows start flush with the sidebar edge: the 3px accent bar is the only + thing before the content. A chevron is rendered only for folders with + children, so leaves carry no dead indent. */ + .folder-row { + gap: var(--s-1); + padding: var(--s-1) var(--s-2) var(--s-1) var(--s-2); + min-height: 40px; + font-size: var(--fz-sm); + color: var(--text-muted); + transition: background .1s, color .1s; + } + .folder-list li:hover > .folder-row { color: var(--text); } + .folder-list li.selected > .folder-row { color: var(--text); } + .folder-list li.selected > .folder-row .folder-name { color: var(--text); font-weight: bold; } + .tree-chevron { + width: 22px; height: 28px; margin-left: -2px; + font-size: 13px; + } + .tree-children { + padding-left: 0 !important; + margin-left: 14px !important; + margin-top: 1px; + } + .folder-row-main { gap: 0; line-height: 1.3; } + .folder-name-row { gap: var(--s-2); } + .folder-name-row > .folder-name { font-size: var(--fz-sm); } + .folder-count { + flex-shrink: 0; + min-width: 1.6em; padding: 0 var(--s-1); + border-radius: 999px; + background: var(--surface); + color: var(--text-muted); + font-size: var(--fz-xs); line-height: 1.5; + text-align: center; letter-spacing: 0; + transition: background .1s, color .1s; + } + .folder-count::before { content: none; } + .folder-count[data-count="0"] { color: var(--text-dim); background: transparent; box-shadow: inset 0 0 0 1px var(--border); } + .folder-list li.selected > .folder-row .folder-count { background: var(--bg); color: var(--text); } + .folder-row .folder-scope { + width: 16px; height: 16px; + font-size: 16px; + color: var(--text-dim); + } + .folder-list li:hover > .folder-row .folder-scope, + .folder-list li.selected > .folder-row .folder-scope { color: var(--accent); } + .folder-desc { line-height: 1.25; } + + /* UX (#254) selection feedback: jump to top, change animation, skeleton */ + /* Title slides in from the left and the action bar's bottom border + flashes accent when the selected folder changes. The .changed class + is added by announceFolderShown() and removed on animationend so + it can replay on the next selection. */ + #entries-header.changed #entries-title { + animation: rg-title-in 350ms ease-out both; + } + #entries-header.changed .folder-actions { + animation: rg-header-flash 700ms ease-out both; + } + @keyframes rg-title-in { + from { opacity: 0; transform: translateX(-4px); } + to { opacity: 1; transform: none; } + } + @keyframes rg-header-flash { + 0% { border-bottom-color: var(--accent); } + 60% { border-bottom-color: var(--accent); } + 100% { border-bottom-color: var(--border); } + } + /* Skeleton: a real .entries-table so column padding, header height and + the 38px Connect-button row height are identical to the loaded + table; only the cells are replaced by shimmering bars. */ + .entries-table.skeleton tbody tr:hover { background: inherit; } + .sk-bar { + display: inline-block; vertical-align: middle; + height: 1em; width: 60%; max-width: 100%; + border-radius: var(--radius); + background: var(--surface) + linear-gradient(90deg, transparent 0%, var(--input) 50%, transparent 100%) + no-repeat; + background-size: 45% 100%; + animation: rg-shimmer 1.2s ease-in-out infinite; + } + .sk-bar.sk-btn { height: var(--ctl-sm); width: 130px; } + .sk-bar.sk-badge { height: 1.4em; width: 3.2em; } + .sk-bar.sk-check { height: 16px; width: 16px; } + .entries-table.skeleton tbody tr:nth-child(2) td > .sk-bar:only-child:not([class*=" sk-"]) { width: 72%; } + .entries-table.skeleton tbody tr:nth-child(3) td > .sk-bar:only-child:not([class*=" sk-"]) { width: 48%; } + .entries-table.skeleton tbody tr:nth-child(4) td > .sk-bar:only-child:not([class*=" sk-"]) { width: 66%; } + @keyframes rg-shimmer { + from { background-position: -100% 0; } + to { background-position: 250% 0; } + } + @media (prefers-reduced-motion: reduce) { + #entries-header.changed #entries-title, + #entries-header.changed .folder-actions, + .sk-bar { animation: none; } + /* Still mark the change without motion: a static accent border + while .changed is on (flashEntriesHeader clears it by timer). */ + #entries-header.changed .folder-actions { border-bottom-color: var(--accent); } + } @@ -444,9 +568,13 @@

No folders yet