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() {
|
export default function MainLayout() {
|
||||||
const [resizing, setResizing] = useState(false)
|
const [resizing, setResizing] = useState(false)
|
||||||
const { resizing: railResizing } = useRailContext()
|
const {
|
||||||
|
resizing: railResizing,
|
||||||
|
handlePaneCollapse: collapseRail,
|
||||||
|
isOpen: railIsOpen,
|
||||||
|
} = useRailContext()
|
||||||
const {
|
const {
|
||||||
togglePdfPane,
|
togglePdfPane,
|
||||||
handlePdfPaneExpand,
|
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
|
// verticalSplit is always vertical; sideBySide becomes vertical on mobile
|
||||||
const isVertical =
|
const isVertical =
|
||||||
pdfLayout === 'verticalSplit' ||
|
pdfLayout === 'verticalSplit' ||
|
||||||
|
|||||||
+17
-13
@@ -370,7 +370,7 @@ export function ProjectListLumiere() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Mobile-only: filter pills + zoom control */}
|
{/* Mobile-only: filter pills */}
|
||||||
<div className="d-flex d-md-none lumiere-mobile-toolbar">
|
<div className="d-flex d-md-none lumiere-mobile-toolbar">
|
||||||
<div
|
<div
|
||||||
className="lumiere-mobile-filters"
|
className="lumiere-mobile-filters"
|
||||||
@@ -389,8 +389,23 @@ export function ProjectListLumiere() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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
|
<div
|
||||||
className="lumiere-zoom-control lumiere-mobile-zoom"
|
className="lumiere-zoom-control lumiere-mobile-zoom d-md-none"
|
||||||
role="group"
|
role="group"
|
||||||
aria-label={t('card_size')}
|
aria-label={t('card_size')}
|
||||||
>
|
>
|
||||||
@@ -407,17 +422,6 @@ export function ProjectListLumiere() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
{selectedProjects.length > 0 && (
|
{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;
|
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)
|
// Search bar — wide enough to show the full placeholder (relaxed on mobile)
|
||||||
form.project-search .form-control {
|
form.project-search .form-control {
|
||||||
min-width: 360px;
|
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) {
|
@media (max-width: 767px) {
|
||||||
|
|
||||||
// Stack the header vertically so nothing overflows the viewport width.
|
// 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 {
|
.lumiere-header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stack header-actions vertically: search on its own row, then new-project+zoom
|
||||||
.lumiere-header-actions {
|
.lumiere-header-actions {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-shrink: 1;
|
flex-direction: column;
|
||||||
min-width: 0;
|
align-items: stretch;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
form.project-search {
|
form.project-search {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
min-width: 0;
|
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
|
// Compact row: 2-line layout
|
||||||
// Row 1: [checkbox] [name+tags] [⋮ action]
|
// Row 1: [checkbox] [name+tags] [⋮ action]
|
||||||
// Row 2: [checkbox] [format · owner · date]
|
// 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 ───────────────────────────
|
// ── Mobile toolbar: filter tabs ──────────────────────────────────────────
|
||||||
// Two-row layout: filter tabs on top (wrapping), zoom on bottom-right.
|
// flex-wrap:wrap on the filter container makes all tabs in the same row
|
||||||
// flex-wrap:wrap on the filter container is unconditionally safe (no
|
// equal-width regardless of label length.
|
||||||
// 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.
|
|
||||||
|
|
||||||
.lumiere-mobile-toolbar {
|
.lumiere-mobile-toolbar {
|
||||||
flex-direction: column;
|
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%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom buttons when rendered inside lumiere-header-actions-row on mobile
|
||||||
.lumiere-mobile-zoom {
|
.lumiere-mobile-zoom {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
align-self: flex-end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter tabs: equal-width per row, teal underline on active
|
// Filter tabs: equal-width per row, teal underline on active
|
||||||
|
|||||||
Reference in New Issue
Block a user