feat(editor): account block fuses into the explorer footer

The floating corner box sat ON TOP of the file tree — create enough files
and the last rows scrolled underneath it. The account + language block is
now the explorer panel's footer (VS Code style): the tree scrolls above
it, always. When the explorer is collapsed the block falls back to the
small fixed corner box, so account and language stay reachable.

The standalone language globe becomes a logged-out affordance: once the
pro auth slot renders its signed-in marker (data-auth-user), a :has()
rule hides the globe — signed-in users change language inside the
account menu, where the pro overlay now hosts it.
This commit is contained in:
David Montero Crespo 2026-07-30 20:54:24 +02:00
parent 57d7c93c90
commit 3b7abfc4f7
4 changed files with 66 additions and 17 deletions

View File

@ -615,3 +615,25 @@ body {
.app-header--with-toolbar .header-editor-toolbar .unified-toolbar { .app-header--with-toolbar .header-editor-toolbar .unified-toolbar {
padding-right: 6px !important; padding-right: 6px !important;
} }
/* Explorer account footer
The account + language block fused to the bottom of the file explorer:
a long tree scrolls above it instead of hiding under a floating box.
(.editor-corner-box remains as the fallback when the explorer is
collapsed.) The standalone globe is a logged-out affordance only once
the pro auth slot renders its signed-in marker, language moves inside
the account menu and the globe hides. */
.explorer-account-footer {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
border-top: 1px solid #2f3542;
background: #171a21;
}
.explorer-account-footer:has([data-auth-user]) .corner-lang,
.editor-corner-box:has([data-auth-user]) .corner-lang {
display: none;
}

View File

@ -1,5 +1,4 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useProjectStore } from '../../store/useProjectStore'; import { useProjectStore } from '../../store/useProjectStore';
@ -309,20 +308,9 @@ export const AppHeader: React.FC<AppHeaderProps> = ({ autoSave, editorMenu, edit
)} )}
</div> </div>
{/* Editor corner box bottom-left, IDE style: the account button {/* The account + language block lives in the file-explorer footer now
(pro injects it into the auth slot) and the language switcher. (EditorPage renders it) fused so a long file tree never scrolls
Portaled to <body>: the header's backdrop-filter makes it a underneath a floating box. */}
containing block, so a fixed child positioned inside it lands at
the header's bottom (the top of the page) instead of the
viewport's measured at y=-4, on top of the logo. */}
{editorToolbar &&
createPortal(
<div className="editor-corner-box">
<LanguageSwitcher />
<div data-velxio-slot="header-auth" style={{ display: 'contents' }} />
</div>,
document.body,
)}
{showShareModal && <ShareModal onClose={() => setShowShareModal(false)} />} {showShareModal && <ShareModal onClose={() => setShowShareModal(false)} />}
</header> </header>

View File

@ -84,3 +84,10 @@
bottom: 110%; bottom: 110%;
margin: 0 0 6px; margin: 0 0 6px;
} }
/* Same drop-up need at the explorer footer. */
.explorer-account-footer .velxio-language-menu {
top: auto;
bottom: 110%;
margin: 0 0 6px;
}

View File

@ -29,6 +29,7 @@ import { showConfirmDialog } from '../store/useMessageDialogStore';
import { useAutoSaveProject } from '../hooks/useAutoSaveProject'; import { useAutoSaveProject } from '../hooks/useAutoSaveProject';
import { registerEditorCommand } from '../lib/editorCommands'; import { registerEditorCommand } from '../lib/editorCommands';
import { EditorMenuBar } from '../components/editor/EditorMenuBar'; import { EditorMenuBar } from '../components/editor/EditorMenuBar';
import { LanguageSwitcher } from '../components/layout/LanguageSwitcher';
import type { CompilationLog } from '../utils/compilationLogger'; import type { CompilationLog } from '../utils/compilationLogger';
import '../App.css'; import '../App.css';
@ -385,6 +386,22 @@ export const EditorPage: React.FC = () => {
there used to be 44+38. On narrow widths the strip wraps internally there used to be 44+38. On narrow widths the strip wraps internally
and the header grows; the docked AI chat is avoided by the same and the header grows; the docked AI chat is avoided by the same
padding-right the strip always had. */ padding-right the strip always had. */
/* Account + language block. Fused as the explorer panel's footer so a
long file tree scrolls ABOVE it instead of disappearing underneath a
floating box; when the explorer is collapsed it falls back to the
small fixed corner box. The standalone language switcher only shows
while logged OUT signed-in users change language inside the account
menu (see pro HeaderAuth), and a CSS :has() rule hides the globe when
the auth slot renders a logged-in marker. */
const accountBlock = !isMobile ? (
<>
<span className="corner-lang">
<LanguageSwitcher />
</span>
<div data-velxio-slot="header-auth" style={{ display: 'contents' }} />
</>
) : undefined;
const unifiedToolbar = !isMobile ? ( const unifiedToolbar = !isMobile ? (
<div className="unified-toolbar"> <div className="unified-toolbar">
<button <button
@ -558,10 +575,21 @@ export const EditorPage: React.FC = () => {
{explorerOpen && ( {explorerOpen && (
<> <>
<div <div
style={{ width: explorerWidth, flexShrink: 0, display: 'flex', overflow: 'hidden' }} style={{
width: explorerWidth,
flexShrink: 0,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}}
> >
<div style={{ flex: 1, minHeight: 0, display: 'flex', overflow: 'hidden' }}>
<FileExplorer onSaveClick={handleSaveClick} onNewClick={handleNewClick} /> <FileExplorer onSaveClick={handleSaveClick} onNewClick={handleNewClick} />
</div> </div>
{accountBlock && (
<div className="explorer-account-footer">{accountBlock}</div>
)}
</div>
{!isMobile && ( {!isMobile && (
<div <div
className="explorer-resize-handle" className="explorer-resize-handle"
@ -571,6 +599,10 @@ export const EditorPage: React.FC = () => {
</> </>
)} )}
{!explorerOpen && accountBlock && (
<div className="editor-corner-box">{accountBlock}</div>
)}
{/* Editor main area */} {/* Editor main area */}
<div <div
style={{ style={{