Fit the layout between phone and desktop widths
From about 920px down to the phone layout (700px), the sidebar kept its full width and the topbar's credit balance, Learning Lab link and language switch ran past the right edge. Between 701 and 1024px the sidebar now starts as the icon rail (the toggle opens it for the visit), and up to 1100px the topbar and the page head's buttons wrap onto another line, without the engine label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
import { useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react'
|
||||||
import { useClerk, useOrganization, useOrganizationList, useUser } from '@clerk/react'
|
import { useClerk, useOrganization, useOrganizationList, useUser } from '@clerk/react'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { NavLink, Outlet, useLocation } from 'react-router-dom'
|
import { NavLink, Outlet, useLocation } from 'react-router-dom'
|
||||||
@@ -130,6 +130,16 @@ function OrgSwitcher() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remembered per browser; storage can throw in private windows, so it falls back to expanded.
|
// Remembered per browser; storage can throw in private windows, so it falls back to expanded.
|
||||||
|
// Between the phone layout and a full desktop, the sidebar would leave too little room for the
|
||||||
|
// page and the topbar, so it starts as the icon rail there; the toggle still opens it for this visit.
|
||||||
|
const NARROW = '(min-width: 701px) and (max-width: 1024px)'
|
||||||
|
function useNarrow() {
|
||||||
|
return useSyncExternalStore(
|
||||||
|
notify => { const m = matchMedia(NARROW); m.addEventListener('change', notify); return () => m.removeEventListener('change', notify) },
|
||||||
|
() => matchMedia(NARROW).matches,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const COLLAPSED_KEY = 'sidebar-collapsed'
|
const COLLAPSED_KEY = 'sidebar-collapsed'
|
||||||
function readCollapsed() {
|
function readCollapsed() {
|
||||||
try { return localStorage.getItem(COLLAPSED_KEY) === '1' } catch { return false }
|
try { return localStorage.getItem(COLLAPSED_KEY) === '1' } catch { return false }
|
||||||
@@ -149,11 +159,17 @@ export function AppLayout() {
|
|||||||
// In braces: Chrome's scrollTo now returns a Promise, which React would keep as the effect's
|
// In braces: Chrome's scrollTo now returns a Promise, which React would keep as the effect's
|
||||||
// cleanup and call on the next route change ("is not a function"), unmounting the whole app.
|
// cleanup and call on the next route change ("is not a function"), unmounting the whole app.
|
||||||
useLayoutEffect(() => { window.scrollTo(0, 0) }, [pathname])
|
useLayoutEffect(() => { window.scrollTo(0, 0) }, [pathname])
|
||||||
const [collapsed, setCollapsed] = useState(readCollapsed)
|
const [saved, setSaved] = useState(readCollapsed)
|
||||||
const toggleCollapsed = () => setCollapsed(c => {
|
const narrow = useNarrow()
|
||||||
|
const [openNarrow, setOpenNarrow] = useState(false)
|
||||||
|
const collapsed = narrow ? !openNarrow : saved
|
||||||
|
const toggleCollapsed = () => {
|
||||||
|
if (narrow) return setOpenNarrow(o => !o)
|
||||||
|
setSaved(c => {
|
||||||
try { localStorage.setItem(COLLAPSED_KEY, c ? '0' : '1') } catch { /* keep it for this visit only */ }
|
try { localStorage.setItem(COLLAPSED_KEY, c ? '0' : '1') } catch { /* keep it for this visit only */ }
|
||||||
return !c
|
return !c
|
||||||
})
|
})
|
||||||
|
}
|
||||||
const name = user?.fullName || user?.primaryEmailAddress?.emailAddress || '-'
|
const name = user?.fullName || user?.primaryEmailAddress?.emailAddress || '-'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -428,3 +428,13 @@ dialog::backdrop{background:rgba(16,24,40,.45);backdrop-filter:blur(4px)}
|
|||||||
}
|
}
|
||||||
@media(max-width:700px){.sidebar-toggle,.sidebar-help-icon{display:none}}
|
@media(max-width:700px){.sidebar-toggle,.sidebar-help-icon{display:none}}
|
||||||
@media(prefers-reduced-motion:reduce){.sidebar,.workspace-main{transition:none}}
|
@media(prefers-reduced-motion:reduce){.sidebar,.workspace-main{transition:none}}
|
||||||
|
|
||||||
|
/* Tablet widths: the topbar wraps instead of pushing its pills past the edge, and the page head's
|
||||||
|
buttons go under the title when they no longer fit beside it */
|
||||||
|
@media(min-width:701px) and (max-width:1100px){
|
||||||
|
.topbar{height:auto;min-height:64px;padding-top:10px;padding-bottom:10px;flex-wrap:wrap;row-gap:8px}
|
||||||
|
.topbar-right{flex-wrap:wrap;justify-content:flex-end;gap:8px 10px}
|
||||||
|
.demo-indicator{display:none}
|
||||||
|
.page-head{flex-wrap:wrap}
|
||||||
|
.page-head .head-actions{flex-wrap:wrap}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user