mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-07-30 09:48:47 -04:00
Refactor profile layout and navigation: align BackButton with form padding, simplify ProfileFormSectionBar behavior, adjust spacing for consistent visual hierarchy.
This commit is contained in:
@@ -22,7 +22,10 @@ export function BackButton(props: {className?: string}) {
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className={clsx(
|
||||
'text-ink-500 hover:text-primary-700 inline-flex items-center gap-1 text-sm transition-all',
|
||||
// `-m-2 p-2` is hit area only: on mobile the label is hidden and the button was the 16px
|
||||
// chevron itself, well under a thumb's worth of target. The negative margin cancels the
|
||||
// padding, so the chevron stays exactly where every caller already positions it.
|
||||
'text-ink-500 hover:text-primary-700 -m-2 inline-flex items-center gap-1 p-2 text-sm transition-all',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -134,31 +134,17 @@ export function ProfileFormNav(props: {className?: string}) {
|
||||
* length. The need is greater here than on desktop, since every field stacks and the page runs
|
||||
* roughly three times longer; the answer just has to cost one row instead of a screen.
|
||||
*
|
||||
* It hides while you scroll down and returns when you scroll up. A permanent bar would spend 44px of
|
||||
* an already short viewport on something wanted a few times per session, and the bottom edge is
|
||||
* already taken by the nav bar and the floating Save button.
|
||||
* It stays pinned for the whole form. It previously hid on scroll down to save 44px of a short
|
||||
* viewport, but a bar that is only there when you scroll the wrong way reads as gone — you cannot
|
||||
* see where you are without scrolling backwards first, which is exactly what the index exists to
|
||||
* avoid.
|
||||
*/
|
||||
export function ProfileFormSectionBar(props: {className?: string}) {
|
||||
const {className} = props
|
||||
const {sections, activeId, activeIndex, goTo} = useFormSections()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [hidden, setHidden] = useState(false)
|
||||
const t = useT()
|
||||
|
||||
useEffect(() => {
|
||||
let lastY = window.scrollY
|
||||
const onScroll = () => {
|
||||
const y = window.scrollY
|
||||
// The 8px deadband stops momentum jitter from flickering the bar on and off.
|
||||
if (Math.abs(y - lastY) > 8) {
|
||||
setHidden(y > lastY && y > 160)
|
||||
lastY = y
|
||||
}
|
||||
}
|
||||
window.addEventListener('scroll', onScroll, {passive: true})
|
||||
return () => window.removeEventListener('scroll', onScroll)
|
||||
}, [])
|
||||
|
||||
if (sections.length === 0) return null
|
||||
|
||||
const active = sections[activeIndex]
|
||||
@@ -169,8 +155,7 @@ export function ProfileFormSectionBar(props: {className?: string}) {
|
||||
status bar in the Capacitor shell; the variable is 0 in a browser, so one rule covers both. */}
|
||||
<div
|
||||
className={clsx(
|
||||
'bg-canvas-100/95 border-canvas-300 sticky top-[var(--tnh)] z-20 -mx-6 border-b px-6 backdrop-blur transition-transform duration-200',
|
||||
hidden && '-translate-y-full',
|
||||
'bg-canvas-100/95 border-canvas-300 sticky top-[var(--tnh)] z-20 -mx-6 border-b px-6 backdrop-blur',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -100,7 +100,7 @@ export function ProfileInfo(props: {
|
||||
Pull it up through the body's safe-area padding and add that inset back as padding, so the
|
||||
bar's background fills the status-bar strip while its buttons stay clear of the native nav. */}
|
||||
<div
|
||||
className="bg-canvas-50/95 border-canvas-300 sticky top-0 z-20 mb-6 flex items-center border-b px-4 sm:px-9 py-4 -mt-[env(safe-area-inset-top)] pt-[calc(1rem+env(safe-area-inset-top))] backdrop-blur"
|
||||
className="bg-canvas-50/95 border-canvas-300 top-0 z-20 mb-6 flex items-center border-b px-4 sm:px-9 py-4 backdrop-blur"
|
||||
style={{
|
||||
borderBottomWidth: '1px',
|
||||
gap: '10px',
|
||||
|
||||
@@ -112,7 +112,9 @@ function ProfilePageInner(props: {user: User; profile: Profile}) {
|
||||
url={`/profile`}
|
||||
/>
|
||||
<Col className="w-full">
|
||||
<BackButton className="ml-2 mb-2 self-start" />
|
||||
{/* `ml-4` rather than `ml-2`: the button carries 2 of hit-area padding, so this is what puts
|
||||
the chevron on the same left edge as the form column's `px-6` below. */}
|
||||
<BackButton className="ml-4 mb-2 self-start" />
|
||||
{/* The index sits in the left gutter from `xl` up, where there is room for it beside a form
|
||||
capped at 3xl. Below that it is hidden rather than stacked: a list of eighteen links above
|
||||
the first field would be a second thing to read before starting. */}
|
||||
@@ -120,7 +122,7 @@ function ProfilePageInner(props: {user: User; profile: Profile}) {
|
||||
<ProfileFormNav className="sticky top-8 hidden h-fit w-52 shrink-0 xl:flex" />
|
||||
<Col className={'w-full min-w-0 max-w-3xl'}>
|
||||
{/* Same index, one row instead of a column, wherever the rail does not fit. */}
|
||||
<ProfileFormSectionBar className="xl:hidden" />
|
||||
<ProfileFormSectionBar className="mb-4 xl:hidden" />
|
||||
<RequiredProfileUserForm
|
||||
data={baseUser}
|
||||
setData={setBaseUserState}
|
||||
|
||||
@@ -146,7 +146,9 @@ export default function SignupPage() {
|
||||
// the same form gets on /profile, with the page's whole right half empty.
|
||||
<Col className={'w-full px-6 py-4'}>
|
||||
<div className="mx-auto w-full max-w-4xl">
|
||||
<BackButton className="-ml-2 mb-2 self-start" />
|
||||
{/* No negative margin: the chevron lines up with the column's left edge, like every
|
||||
other thing on the page. Pulled out by 2 it hung into the page gutter. */}
|
||||
<BackButton className="mb-2 self-start" />
|
||||
{/* The flow had no progress indicator at all: two substantial form steps that looked alike,
|
||||
so the only way to learn there were two was to finish the first. The bar is deliberately
|
||||
the same component as /onboarding — those three screens lead directly here, and a reader
|
||||
@@ -158,7 +160,10 @@ export default function SignupPage() {
|
||||
current: step + 1,
|
||||
total: TOTAL_STEPS,
|
||||
})}
|
||||
className="mb-7"
|
||||
// Step 2 puts the section bar directly under this. `mb-7` plus the form row's own top
|
||||
// padding left 44px of empty page between the two, which read as the bar belonging to
|
||||
// the form below rather than to the header above it.
|
||||
className={step === 1 ? 'mb-4' : 'mb-7'}
|
||||
/>
|
||||
</div>
|
||||
{step === 0 ? (
|
||||
@@ -175,10 +180,10 @@ export default function SignupPage() {
|
||||
// Same index as /profile: this is the same eighteen-section form, and it is longer here
|
||||
// because nothing is filled in yet. Hidden below `xl`, where there is no gutter to put it
|
||||
// in without pushing a list of links above the first field.
|
||||
<div className={'mx-auto mb-2 flex w-full max-w-6xl gap-10 py-4'}>
|
||||
<div className={'mx-auto mb-2 flex w-full max-w-6xl gap-10 pb-4'}>
|
||||
<ProfileFormNav className="sticky top-8 hidden h-fit w-52 shrink-0 xl:flex" />
|
||||
<Col className={'w-full min-w-0 max-w-3xl'}>
|
||||
<ProfileFormSectionBar className="xl:hidden" />
|
||||
<ProfileFormSectionBar className="mb-4 xl:hidden" />
|
||||
<OptionalProfileUserForm
|
||||
profile={profileForm}
|
||||
setProfile={setProfileState}
|
||||
|
||||
Reference in New Issue
Block a user