mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 17:43:04 +00:00
Fix drawer snap geometry: handle at bottom, scroll on content ref
- Reorder drawer so handle sits at the screen bottom (snap shows bottom band) - Hide dock content when collapsed instead of clipping through search - Attach scroll-collapse listener directly on campaign-content-scroll ref
This commit is contained in:
parent
923a0eb885
commit
a056616d4a
6 changed files with 50 additions and 59 deletions
|
|
@ -8,10 +8,7 @@ import {
|
|||
CampaignQuickActionsHost,
|
||||
useQuickActionsChromeActive,
|
||||
} from '@/components/campaign/shell/CampaignQuickActionsHost'
|
||||
import {
|
||||
CampaignQuickActionsScrollCollapse,
|
||||
CampaignQuickActionsSnapProvider,
|
||||
} from '@/components/campaign/shell/CampaignQuickActionsSnapContext'
|
||||
import { CampaignQuickActionsSnapProvider } from '@/components/campaign/shell/CampaignQuickActionsSnapContext'
|
||||
import type { CampaignRole } from '@/lib/campaignRoles'
|
||||
|
||||
export const CampaignAppScrollChrome = ({
|
||||
|
|
@ -30,7 +27,6 @@ export const CampaignAppScrollChrome = ({
|
|||
return (
|
||||
<CampaignQuickActionsSnapProvider>
|
||||
<CampaignGlobalSearchProvider>
|
||||
<CampaignQuickActionsScrollCollapse />
|
||||
<CampaignContentScroll quickActionsPeek>{children}</CampaignContentScroll>
|
||||
<CampaignQuickActionsHost role={role} />
|
||||
</CampaignGlobalSearchProvider>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
type QuickActionsSnapPoint,
|
||||
} from '@/lib/campaignQuickActionSnap'
|
||||
import type { CampaignQuickAction } from '@/lib/campaignQuickActionTypes'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const SNAP_POINTS = [...QUICK_ACTIONS_SNAP_POINTS]
|
||||
|
||||
|
|
@ -67,20 +68,14 @@ export const CampaignQuickActionsDrawer = ({
|
|||
className="border-t border-border bg-background text-foreground shadow-[0_-4px_24px_-8px_rgb(0_0_0/0.12)] print:hidden"
|
||||
>
|
||||
<DrawerTitle className="sr-only">Ações rápidas</DrawerTitle>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleSnap}
|
||||
aria-expanded={isDock}
|
||||
aria-controls="quickActionContext"
|
||||
className="flex w-full shrink-0 cursor-grab flex-col items-center border-0 bg-transparent px-4 pt-2 pb-1 active:cursor-grabbing"
|
||||
aria-label={isDock ? 'Ocultar ações rápidas' : 'Mostrar ações rápidas'}
|
||||
>
|
||||
<span aria-hidden className="mb-1 block h-1 w-12 rounded-full bg-muted" />
|
||||
</button>
|
||||
<div
|
||||
id="quickActionContext"
|
||||
className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto overscroll-contain px-4 pb-[max(1rem,env(safe-area-inset-bottom,0px))]"
|
||||
className={cn(
|
||||
'flex min-h-0 flex-col gap-4 overflow-hidden px-4 pt-2',
|
||||
isDock ? 'flex-1' : 'hidden',
|
||||
)}
|
||||
data-snap={isDock ? 'dock' : 'collapsed'}
|
||||
aria-hidden={!isDock}
|
||||
>
|
||||
{showActions ? (
|
||||
<CampaignHomeActionStrip
|
||||
|
|
@ -96,6 +91,16 @@ export const CampaignQuickActionsDrawer = ({
|
|||
) : null}
|
||||
<CampaignGlobalSearchBody />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleSnap}
|
||||
aria-expanded={isDock}
|
||||
aria-controls="quickActionContext"
|
||||
className="flex w-full shrink-0 cursor-grab flex-col items-center border-0 bg-transparent px-4 pt-2 pb-[max(0.25rem,env(safe-area-inset-bottom,0px))] active:cursor-grabbing"
|
||||
aria-label={isDock ? 'Ocultar ações rápidas' : 'Mostrar ações rápidas'}
|
||||
>
|
||||
<span aria-hidden className="mb-1 block h-1 w-12 rounded-full bg-muted" />
|
||||
</button>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useCallback, useEffect, useRef, type ReactNode } from 'react'
|
||||
|
||||
import { useHomeSearch } from '@/components/campaign/dashboard/HomeSearchContext'
|
||||
import { useCampaignQuickActionContext } from '@/components/campaign/shell/CampaignQuickActionContext'
|
||||
import { CampaignQuickActionsDrawer } from '@/components/campaign/shell/CampaignQuickActionsDrawer'
|
||||
import { useCampaignQuickActionsSnap } from '@/components/campaign/shell/CampaignQuickActionsSnapContext'
|
||||
|
|
@ -10,6 +11,7 @@ import { useIsMobile } from '@/hooks/use-mobile'
|
|||
import { shouldMountQuickActionsDrawer } from '@/lib/campaignQuickActionMount'
|
||||
import { resolveQuickActionsForPath } from '@/lib/campaignQuickActionRegistry'
|
||||
import {
|
||||
QUICK_ACTIONS_SCROLL_COLLAPSE_THRESHOLD_PX,
|
||||
QUICK_ACTIONS_SNAP_COLLAPSED,
|
||||
QUICK_ACTIONS_SNAP_DOCK,
|
||||
} from '@/lib/campaignQuickActionSnap'
|
||||
|
|
@ -17,11 +19,35 @@ import type { CampaignRole } from '@/lib/campaignRoles'
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
const CampaignContentScrollWithPeek = ({ children }: { children: ReactNode }) => {
|
||||
const { isDock } = useCampaignQuickActionsSnap()
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
const { isDock, setSnapPoint } = useCampaignQuickActionsSnap()
|
||||
const { uiFocused } = useHomeSearch()
|
||||
const peekHeight = isDock ? QUICK_ACTIONS_SNAP_DOCK : QUICK_ACTIONS_SNAP_COLLAPSED
|
||||
|
||||
const collapseIfScrolled = useCallback(() => {
|
||||
if (uiFocused) return
|
||||
|
||||
const scrollport = scrollRef.current
|
||||
if (!scrollport) return
|
||||
|
||||
if (scrollport.scrollTop > QUICK_ACTIONS_SCROLL_COLLAPSE_THRESHOLD_PX) {
|
||||
setSnapPoint(QUICK_ACTIONS_SNAP_COLLAPSED)
|
||||
}
|
||||
}, [setSnapPoint, uiFocused])
|
||||
|
||||
useEffect(() => {
|
||||
const scrollport = scrollRef.current
|
||||
if (!scrollport) return
|
||||
|
||||
scrollport.addEventListener('scroll', collapseIfScrolled, { passive: true })
|
||||
return () => {
|
||||
scrollport.removeEventListener('scroll', collapseIfScrolled)
|
||||
}
|
||||
}, [collapseIfScrolled])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={scrollRef}
|
||||
data-slot="campaign-content-scroll"
|
||||
className={cn(
|
||||
'min-h-0 flex-1 overflow-y-auto overscroll-contain p-4 md:p-6 print:h-auto print:overflow-visible print:p-0',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
import { usePathname } from 'next/navigation'
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
|
|
@ -13,10 +12,7 @@ import {
|
|||
type SetStateAction,
|
||||
} from 'react'
|
||||
|
||||
import { useHomeSearch } from '@/components/campaign/dashboard/HomeSearchContext'
|
||||
import {
|
||||
QUICK_ACTIONS_SCROLL_COLLAPSE_THRESHOLD_PX,
|
||||
QUICK_ACTIONS_SNAP_COLLAPSED,
|
||||
QUICK_ACTIONS_SNAP_DOCK,
|
||||
quickActionsSnapIsDock,
|
||||
type QuickActionsSnapPoint,
|
||||
|
|
@ -63,32 +59,3 @@ export const useCampaignQuickActionsSnap = (): CampaignQuickActionsSnapContextVa
|
|||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/** Collapses the dock when the main content scrollport moves down past the threshold. */
|
||||
export const CampaignQuickActionsScrollCollapse = () => {
|
||||
const { setSnapPoint } = useCampaignQuickActionsSnap()
|
||||
const { uiFocused } = useHomeSearch()
|
||||
|
||||
const collapseIfScrolled = useCallback(() => {
|
||||
if (uiFocused) return
|
||||
|
||||
const scrollport = document.querySelector('[data-slot="campaign-content-scroll"]')
|
||||
if (!(scrollport instanceof HTMLElement)) return
|
||||
|
||||
if (scrollport.scrollTop > QUICK_ACTIONS_SCROLL_COLLAPSE_THRESHOLD_PX) {
|
||||
setSnapPoint(QUICK_ACTIONS_SNAP_COLLAPSED)
|
||||
}
|
||||
}, [setSnapPoint, uiFocused])
|
||||
|
||||
useEffect(() => {
|
||||
const scrollport = document.querySelector('[data-slot="campaign-content-scroll"]')
|
||||
if (!(scrollport instanceof HTMLElement)) return
|
||||
|
||||
scrollport.addEventListener('scroll', collapseIfScrolled, { passive: true })
|
||||
return () => {
|
||||
scrollport.removeEventListener('scroll', collapseIfScrolled)
|
||||
}
|
||||
}, [collapseIfScrolled])
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -437,8 +437,8 @@ test.describe('Municípios — bottom drawer mobile (B100)', () => {
|
|||
el.dispatchEvent(new Event('scroll'))
|
||||
})
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Mostrar ações rápidas' })).toBeVisible()
|
||||
await expect(search).not.toBeInViewport()
|
||||
await expect(page.getByRole('button', { name: 'Mostrar ações rápidas' })).toBeInViewport()
|
||||
|
||||
await scrollport.evaluate((el) => {
|
||||
el.scrollTop = 0
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { CampaignGlobalSearchProvider } from '@/components/campaign/dashboard/Ca
|
|||
import { CampaignQuickActionsDrawer } from '@/components/campaign/shell/CampaignQuickActionsDrawer'
|
||||
import { CampaignContentScroll } from '@/components/campaign/shell/CampaignQuickActionsHost'
|
||||
import {
|
||||
CampaignQuickActionsScrollCollapse,
|
||||
CampaignQuickActionsSnapProvider,
|
||||
useCampaignQuickActionsSnap,
|
||||
} from '@/components/campaign/shell/CampaignQuickActionsSnapContext'
|
||||
|
|
@ -41,10 +40,7 @@ const idleSuggestPayload = {
|
|||
const renderQuickActionsChrome = (ui: ReactNode) =>
|
||||
render(
|
||||
<CampaignQuickActionsSnapProvider>
|
||||
<CampaignGlobalSearchProvider>
|
||||
<CampaignQuickActionsScrollCollapse />
|
||||
{ui}
|
||||
</CampaignGlobalSearchProvider>
|
||||
<CampaignGlobalSearchProvider>{ui}</CampaignGlobalSearchProvider>
|
||||
</CampaignQuickActionsSnapProvider>,
|
||||
)
|
||||
|
||||
|
|
@ -68,7 +64,7 @@ describe('CampaignQuickActionsDrawer (B100)', () => {
|
|||
renderQuickActionsChrome(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
const context = document.getElementById('quickActionContext')
|
||||
expect(context?.className).not.toContain('hidden')
|
||||
expect(context?.className.split(/\s+/)).not.toContain('hidden')
|
||||
expect(context?.getAttribute('data-snap')).toBe('dock')
|
||||
expect(screen.getByLabelText('Buscar na campanha')).toBeTruthy()
|
||||
expect(screen.getByRole('region', { name: 'Resultados da busca' })).toBeTruthy()
|
||||
|
|
@ -106,6 +102,7 @@ describe('CampaignQuickActionsDrawer (B100)', () => {
|
|||
expect(document.getElementById('quickActionContext')?.getAttribute('data-snap')).toBe(
|
||||
'collapsed',
|
||||
)
|
||||
expect(document.getElementById('quickActionContext')?.className).toContain('hidden')
|
||||
expect(screen.getByRole('button', { name: 'Mostrar ações rápidas' })).toBeTruthy()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' }))
|
||||
|
|
@ -113,7 +110,7 @@ describe('CampaignQuickActionsDrawer (B100)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('CampaignQuickActionsScrollCollapse (B100)', () => {
|
||||
describe('CampaignContentScroll quick-actions collapse (B100)', () => {
|
||||
it('collapses on content scroll down but not when scrolling back to top', () => {
|
||||
renderQuickActionsChrome(
|
||||
<>
|
||||
|
|
|
|||
Loading…
Reference in a new issue