feat(mobile): collapse rail by default; tighten project page header layout
Build and Deploy Verso / deploy (push) Has been cancelled
Build and Deploy Verso / deploy (push) Has been cancelled
Editor: - Auto-collapse the file-tree rail on mobile so the editor+PDF panels occupy the full screen width instead of sharing it with a 15% sidebar. The user can still open the rail from the toolbar; the collapse only fires on first load or when switching to a mobile viewport. Project page (Lumiere): - Move the XS/M/L zoom buttons from the mobile filter-pill toolbar into a row with the New Project button, so neither is stranded alone. - The search bar gets its own full-width row above the actions row. - Desktop layout is unchanged (search + new-project stay side-by-side; zoom stays in the title row). `display:contents` makes the wrapper div transparent to the desktop flex container. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,11 @@ function detectMobile() {
|
||||
|
||||
export default function MainLayout() {
|
||||
const [resizing, setResizing] = useState(false)
|
||||
const { resizing: railResizing } = useRailContext()
|
||||
const {
|
||||
resizing: railResizing,
|
||||
handlePaneCollapse: collapseRail,
|
||||
isOpen: railIsOpen,
|
||||
} = useRailContext()
|
||||
const {
|
||||
togglePdfPane,
|
||||
handlePdfPaneExpand,
|
||||
@@ -65,6 +69,16 @@ export default function MainLayout() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// On mobile, collapse the file-tree rail so editor+PDF panels get full width.
|
||||
useEffect(() => {
|
||||
if (isMobile && railIsOpen) {
|
||||
collapseRail()
|
||||
}
|
||||
// Only run on mount and when isMobile changes — don't re-collapse if the
|
||||
// user manually re-opens the rail during the session.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isMobile])
|
||||
|
||||
// verticalSplit is always vertical; sideBySide becomes vertical on mobile
|
||||
const isVertical =
|
||||
pdfLayout === 'verticalSplit' ||
|
||||
|
||||
+17
-13
@@ -370,7 +370,7 @@ export function ProjectListLumiere() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* Mobile-only: filter pills + zoom control */}
|
||||
{/* Mobile-only: filter pills */}
|
||||
<div className="d-flex d-md-none lumiere-mobile-toolbar">
|
||||
<div
|
||||
className="lumiere-mobile-filters"
|
||||
@@ -389,8 +389,23 @@ export function ProjectListLumiere() {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="lumiere-header-actions">
|
||||
<SearchForm
|
||||
inputValue={searchText}
|
||||
setInputValue={setSearchText}
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
/>
|
||||
{/* On mobile: new project + zoom share a row below the search bar */}
|
||||
<div className="lumiere-header-actions-row">
|
||||
<NewProjectButton
|
||||
id="lumiere-new-project-button"
|
||||
showAddAffiliationWidget
|
||||
/>
|
||||
{/* Zoom control: on desktop it sits in the title row; on mobile it moves here */}
|
||||
<div
|
||||
className="lumiere-zoom-control lumiere-mobile-zoom"
|
||||
className="lumiere-zoom-control lumiere-mobile-zoom d-md-none"
|
||||
role="group"
|
||||
aria-label={t('card_size')}
|
||||
>
|
||||
@@ -407,17 +422,6 @@ export function ProjectListLumiere() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="lumiere-header-actions">
|
||||
<SearchForm
|
||||
inputValue={searchText}
|
||||
setInputValue={setSearchText}
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
/>
|
||||
<NewProjectButton
|
||||
id="lumiere-new-project-button"
|
||||
showAddAffiliationWidget
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{selectedProjects.length > 0 && (
|
||||
|
||||
@@ -341,6 +341,12 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
// On desktop the inner row wrapper is transparent — new project button flows
|
||||
// naturally inside the flex row alongside the search form.
|
||||
.lumiere-header-actions-row {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
// Search bar — wide enough to show the full placeholder (relaxed on mobile)
|
||||
form.project-search .form-control {
|
||||
min-width: 360px;
|
||||
@@ -894,25 +900,37 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
|
||||
@media (max-width: 767px) {
|
||||
|
||||
// Stack the header vertically so nothing overflows the viewport width.
|
||||
// Without this, lumiere-header-actions (flex-shrink:0) forces the flex
|
||||
// container wider than the screen.
|
||||
.lumiere-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
// Stack header-actions vertically: search on its own row, then new-project+zoom
|
||||
.lumiere-header-actions {
|
||||
width: 100%;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.5rem;
|
||||
|
||||
form.project-search {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// New project button + zoom buttons share a row below the search bar
|
||||
.lumiere-header-actions-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
|
||||
.new-project-dropdown {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Compact row: 2-line layout
|
||||
// Row 1: [checkbox] [name+tags] [⋮ action]
|
||||
// Row 2: [checkbox] [format · owner · date]
|
||||
@@ -977,12 +995,9 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
|
||||
}
|
||||
}
|
||||
|
||||
// ── Mobile toolbar: filter tabs + zoom control ───────────────────────────
|
||||
// Two-row layout: filter tabs on top (wrapping), zoom on bottom-right.
|
||||
// flex-wrap:wrap on the filter container is unconditionally safe (no
|
||||
// overflow-x:auto ancestor chain needed). flex:1 1 auto on each tab makes
|
||||
// all tabs in the same row equal-width regardless of label length, so the
|
||||
// rows align cleanly without producing page overflow on narrow screens.
|
||||
// ── Mobile toolbar: filter tabs ──────────────────────────────────────────
|
||||
// flex-wrap:wrap on the filter container makes all tabs in the same row
|
||||
// equal-width regardless of label length.
|
||||
|
||||
.lumiere-mobile-toolbar {
|
||||
flex-direction: column;
|
||||
@@ -999,9 +1014,9 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Zoom buttons when rendered inside lumiere-header-actions-row on mobile
|
||||
.lumiere-mobile-zoom {
|
||||
flex-shrink: 0;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
// Filter tabs: equal-width per row, teal underline on active
|
||||
|
||||
Reference in New Issue
Block a user