mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
B100 — bottom drawer dock on load + collapse on scroll
- Replace expanded 55dvh snap with fixed DOCK height (12rem) + COLLAPSED handle - Fix Base UI snap geometry via padding-bottom on Drawer popup - Lift snap state to CampaignQuickActionsSnapProvider with pathname reset - Collapse drawer when campaign-content-scroll passes threshold; reopen only via handle/pull - Block collapse while global search is focused or active - Dynamic content-scroll padding for dock vs collapsed peek - Unit tests for dock default, scroll collapse, handle toggle; e2e on municipality detail Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
This commit is contained in:
parent
317935b128
commit
1bb3fc7303
9 changed files with 328 additions and 76 deletions
|
|
@ -2,11 +2,16 @@
|
|||
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import { CampaignGlobalSearchProvider } from '@/components/campaign/dashboard/CampaignGlobalSearchMount'
|
||||
import {
|
||||
CampaignContentScroll,
|
||||
CampaignQuickActionsHost,
|
||||
useQuickActionsChromeActive,
|
||||
} from '@/components/campaign/shell/CampaignQuickActionsHost'
|
||||
import {
|
||||
CampaignQuickActionsScrollCollapse,
|
||||
CampaignQuickActionsSnapProvider,
|
||||
} from '@/components/campaign/shell/CampaignQuickActionsSnapContext'
|
||||
import type { CampaignRole } from '@/lib/campaignRoles'
|
||||
|
||||
export const CampaignAppScrollChrome = ({
|
||||
|
|
@ -16,12 +21,19 @@ export const CampaignAppScrollChrome = ({
|
|||
role: CampaignRole
|
||||
children: ReactNode
|
||||
}) => {
|
||||
const quickActionsPeek = useQuickActionsChromeActive(role)
|
||||
const quickActionsActive = useQuickActionsChromeActive(role)
|
||||
|
||||
if (!quickActionsActive) {
|
||||
return <CampaignContentScroll quickActionsPeek={false}>{children}</CampaignContentScroll>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<CampaignContentScroll quickActionsPeek={quickActionsPeek}>{children}</CampaignContentScroll>
|
||||
<CampaignQuickActionsHost role={role} />
|
||||
</>
|
||||
<CampaignQuickActionsSnapProvider>
|
||||
<CampaignGlobalSearchProvider>
|
||||
<CampaignQuickActionsScrollCollapse />
|
||||
<CampaignContentScroll quickActionsPeek>{children}</CampaignContentScroll>
|
||||
<CampaignQuickActionsHost role={role} />
|
||||
</CampaignGlobalSearchProvider>
|
||||
</CampaignQuickActionsSnapProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
'use client'
|
||||
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { CampaignGlobalSearchMount } from '@/components/campaign/dashboard/CampaignGlobalSearchMount'
|
||||
import { CampaignGlobalSearchBody } from '@/components/campaign/dashboard/CampaignGlobalSearchMount'
|
||||
import { CampaignHomeActionStrip } from '@/components/campaign/dashboard/CampaignHomeActionStrip'
|
||||
import { useCampaignQuickActionsSnap } from '@/components/campaign/shell/CampaignQuickActionsSnapContext'
|
||||
import { Drawer, DrawerContent, DrawerTitle } from '@/components/ui/Drawer'
|
||||
import {
|
||||
QUICK_ACTIONS_SNAP_COLLAPSED,
|
||||
QUICK_ACTIONS_SNAP_EXPANDED,
|
||||
QUICK_ACTIONS_SNAP_DOCK,
|
||||
QUICK_ACTIONS_SNAP_POINTS,
|
||||
quickActionsSnapIsExpanded,
|
||||
quickActionsSnapIsDock,
|
||||
type QuickActionsSnapPoint,
|
||||
} from '@/lib/campaignQuickActionSnap'
|
||||
import type { CampaignQuickAction } from '@/lib/campaignQuickActionTypes'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const SNAP_POINTS = [...QUICK_ACTIONS_SNAP_POINTS]
|
||||
|
||||
|
|
@ -22,36 +22,34 @@ export const CampaignQuickActionsDrawer = ({
|
|||
}: {
|
||||
actions: readonly CampaignQuickAction[]
|
||||
}) => {
|
||||
const [snapPoint, setSnapPoint] = useState<QuickActionsSnapPoint | null>(
|
||||
QUICK_ACTIONS_SNAP_COLLAPSED,
|
||||
)
|
||||
const expanded = quickActionsSnapIsExpanded(snapPoint)
|
||||
const { snapPoint, setSnapPoint, isDock } = useCampaignQuickActionsSnap()
|
||||
const showActions = actions.length > 0
|
||||
|
||||
const handleSnapPointChange = useCallback(
|
||||
(next: QuickActionsSnapPoint | string | number | null) => {
|
||||
if (next === QUICK_ACTIONS_SNAP_COLLAPSED || next === QUICK_ACTIONS_SNAP_EXPANDED) {
|
||||
if (next === QUICK_ACTIONS_SNAP_COLLAPSED || next === QUICK_ACTIONS_SNAP_DOCK) {
|
||||
setSnapPoint(next)
|
||||
return
|
||||
}
|
||||
setSnapPoint(QUICK_ACTIONS_SNAP_COLLAPSED)
|
||||
},
|
||||
[],
|
||||
[setSnapPoint],
|
||||
)
|
||||
|
||||
const toggleSnap = useCallback(() => {
|
||||
setSnapPoint((current) =>
|
||||
quickActionsSnapIsExpanded(current)
|
||||
? QUICK_ACTIONS_SNAP_COLLAPSED
|
||||
: QUICK_ACTIONS_SNAP_EXPANDED,
|
||||
quickActionsSnapIsDock(current) ? QUICK_ACTIONS_SNAP_COLLAPSED : QUICK_ACTIONS_SNAP_DOCK,
|
||||
)
|
||||
}, [])
|
||||
}, [setSnapPoint])
|
||||
|
||||
const handleOpenChange = useCallback((open: boolean) => {
|
||||
if (!open) {
|
||||
setSnapPoint(QUICK_ACTIONS_SNAP_COLLAPSED)
|
||||
}
|
||||
}, [])
|
||||
const handleOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
if (!open) {
|
||||
setSnapPoint(QUICK_ACTIONS_SNAP_COLLAPSED)
|
||||
}
|
||||
},
|
||||
[setSnapPoint],
|
||||
)
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
|
|
@ -66,29 +64,23 @@ export const CampaignQuickActionsDrawer = ({
|
|||
>
|
||||
<DrawerContent
|
||||
id="CampaignQuickActionsDrawer"
|
||||
className={cn(
|
||||
'border-t border-border bg-background text-foreground shadow-[0_-4px_24px_-8px_rgb(0_0_0/0.12)] print:hidden',
|
||||
!expanded && 'pointer-events-auto',
|
||||
)}
|
||||
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={expanded}
|
||||
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={expanded ? 'Ocultar ações rápidas' : 'Mostrar ações rápidas'}
|
||||
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={cn(
|
||||
'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))]',
|
||||
!expanded && 'hidden',
|
||||
)}
|
||||
data-snap={expanded ? 'expanded' : 'collapsed'}
|
||||
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))]"
|
||||
data-snap={isDock ? 'dock' : 'collapsed'}
|
||||
>
|
||||
{showActions ? (
|
||||
<CampaignHomeActionStrip
|
||||
|
|
@ -102,7 +94,7 @@ export const CampaignQuickActionsDrawer = ({
|
|||
className="w-full"
|
||||
/>
|
||||
) : null}
|
||||
{expanded ? <CampaignGlobalSearchMount /> : null}
|
||||
<CampaignGlobalSearchBody />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
|
|
|||
|
|
@ -5,35 +5,55 @@ import type { ReactNode } from 'react'
|
|||
|
||||
import { useCampaignQuickActionContext } from '@/components/campaign/shell/CampaignQuickActionContext'
|
||||
import { CampaignQuickActionsDrawer } from '@/components/campaign/shell/CampaignQuickActionsDrawer'
|
||||
import { useCampaignQuickActionsSnap } from '@/components/campaign/shell/CampaignQuickActionsSnapContext'
|
||||
import { useIsMobile } from '@/hooks/use-mobile'
|
||||
import { shouldMountQuickActionsDrawer } from '@/lib/campaignQuickActionMount'
|
||||
import { resolveQuickActionsForPath } from '@/lib/campaignQuickActionRegistry'
|
||||
import { QUICK_ACTIONS_SNAP_COLLAPSED } from '@/lib/campaignQuickActionSnap'
|
||||
import {
|
||||
QUICK_ACTIONS_SNAP_COLLAPSED,
|
||||
QUICK_ACTIONS_SNAP_DOCK,
|
||||
} from '@/lib/campaignQuickActionSnap'
|
||||
import type { CampaignRole } from '@/lib/campaignRoles'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const CampaignContentScrollWithPeek = ({ children }: { children: ReactNode }) => {
|
||||
const { isDock } = useCampaignQuickActionsSnap()
|
||||
const peekHeight = isDock ? QUICK_ACTIONS_SNAP_DOCK : QUICK_ACTIONS_SNAP_COLLAPSED
|
||||
|
||||
return (
|
||||
<div
|
||||
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',
|
||||
'pb-[calc(1rem+var(--campaign-quick-actions-peek))] md:pb-6',
|
||||
)}
|
||||
style={{ '--campaign-quick-actions-peek': peekHeight } as React.CSSProperties}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CampaignContentScroll = ({
|
||||
children,
|
||||
quickActionsPeek,
|
||||
}: {
|
||||
children: ReactNode
|
||||
quickActionsPeek: boolean
|
||||
}) => (
|
||||
<div
|
||||
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',
|
||||
quickActionsPeek && 'pb-[calc(1rem+var(--campaign-quick-actions-peek))] md:pb-6',
|
||||
)}
|
||||
style={
|
||||
quickActionsPeek
|
||||
? ({ '--campaign-quick-actions-peek': QUICK_ACTIONS_SNAP_COLLAPSED } as React.CSSProperties)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}) => {
|
||||
if (!quickActionsPeek) {
|
||||
return (
|
||||
<div
|
||||
data-slot="campaign-content-scroll"
|
||||
className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-4 md:p-6 print:h-auto print:overflow-visible print:p-0"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <CampaignContentScrollWithPeek>{children}</CampaignContentScrollWithPeek>
|
||||
}
|
||||
|
||||
export const CampaignQuickActionsHost = ({ role }: { role: CampaignRole }) => {
|
||||
const pathname = usePathname()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type Dispatch,
|
||||
type ReactNode,
|
||||
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,
|
||||
} from '@/lib/campaignQuickActionSnap'
|
||||
|
||||
type CampaignQuickActionsSnapContextValue = {
|
||||
snapPoint: QuickActionsSnapPoint | null
|
||||
setSnapPoint: Dispatch<SetStateAction<QuickActionsSnapPoint | null>>
|
||||
isDock: boolean
|
||||
}
|
||||
|
||||
const CampaignQuickActionsSnapContext = createContext<CampaignQuickActionsSnapContextValue | null>(
|
||||
null,
|
||||
)
|
||||
|
||||
export const CampaignQuickActionsSnapProvider = ({ children }: { children: ReactNode }) => {
|
||||
const pathname = usePathname()
|
||||
const [snapPoint, setSnapPoint] = useState<QuickActionsSnapPoint | null>(
|
||||
QUICK_ACTIONS_SNAP_DOCK,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setSnapPoint(QUICK_ACTIONS_SNAP_DOCK)
|
||||
}, [pathname])
|
||||
|
||||
const value = useMemo(
|
||||
(): CampaignQuickActionsSnapContextValue => ({
|
||||
snapPoint,
|
||||
setSnapPoint,
|
||||
isDock: quickActionsSnapIsDock(snapPoint),
|
||||
}),
|
||||
[snapPoint],
|
||||
)
|
||||
|
||||
return (
|
||||
<CampaignQuickActionsSnapContext.Provider value={value}>
|
||||
{children}
|
||||
</CampaignQuickActionsSnapContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useCampaignQuickActionsSnap = (): CampaignQuickActionsSnapContextValue => {
|
||||
const value = useContext(CampaignQuickActionsSnapContext)
|
||||
if (!value) {
|
||||
throw new Error('CampaignQuickActionsSnapProvider is required')
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ const DrawerContent = ({ className, children, ...props }: DrawerPrimitive.Popup.
|
|||
'data-ending-style:transform-(--closed-transform) data-ending-style:opacity-[0.9999] data-ending-style:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-nested-drawer-swiping:duration-0 data-ending-style:data-nested-drawer-swiping:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-starting-style:transform-(--closed-transform) data-swiping:duration-0 data-ending-style:data-swiping:duration-[calc(var(--drawer-swipe-strength)*400ms)]',
|
||||
'data-[swipe-axis=y]:inset-x-0 data-[swipe-axis=y]:data-nested-drawer-open:h-(--stack-height)',
|
||||
'data-[swipe-axis=x]:inset-y-0 data-[swipe-axis=x]:flex-row',
|
||||
'data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:origin-bottom data-[swipe-direction=down]:[--closed-transform:translate3d(0,calc(100%+var(--drawer-inset,0px)+2px),0)] data-[swipe-direction=down]:[--translate-y:calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)-var(--stack-peek-offset)-(var(--stack-shrink)*var(--stack-height)))]',
|
||||
'data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:origin-bottom data-[swipe-direction=down]:[--closed-transform:translate3d(0,calc(100%+var(--drawer-inset,0px)+2px),0)] data-[swipe-direction=down]:[--translate-y:calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)-var(--stack-peek-offset)-(var(--stack-shrink)*var(--stack-height)))] data-[swipe-direction=down]:data-snap-points:pb-[max(0px,calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y)))] data-[swipe-direction=down]:data-snap-points:data-starting-style:pb-0 data-[swipe-direction=down]:data-snap-points:data-ending-style:pb-0',
|
||||
'data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:origin-top data-[swipe-direction=up]:[--closed-transform:translate3d(0,calc(-100%-var(--drawer-inset,0px)-2px),0)] data-[swipe-direction=up]:[--translate-y:calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)+var(--stack-peek-offset)+(var(--stack-shrink)*var(--stack-height)))]',
|
||||
'data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:origin-left data-[swipe-direction=left]:[--closed-transform:translate3d(calc(-100%-var(--drawer-inset,0px)-2px),0,0)] data-[swipe-direction=left]:[--translate-x:calc(var(--drawer-swipe-movement-x)+var(--stack-peek-offset)+(var(--stack-shrink)*100%))]',
|
||||
'data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:origin-right data-[swipe-direction=right]:[--closed-transform:translate3d(calc(100%+var(--drawer-inset,0px)+2px),0,0)] data-[swipe-direction=right]:[--translate-x:calc(var(--drawer-swipe-movement-x)-var(--stack-peek-offset)-(var(--stack-shrink)*100%))]',
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
/** Collapsed peek — swipe handle only (replaces legacy bottom-nav `pb-24`). */
|
||||
export const QUICK_ACTIONS_SNAP_COLLAPSED = '3rem' as const
|
||||
|
||||
/** Expanded — actions strip + global search inside the drawer. */
|
||||
export const QUICK_ACTIONS_SNAP_EXPANDED = 0.55 as const
|
||||
/** Dock — action strip + global search visible on load and after navigation. */
|
||||
export const QUICK_ACTIONS_SNAP_DOCK = '12rem' as const
|
||||
|
||||
/** Scroll down past this threshold on `campaign-content-scroll` collapses the drawer. */
|
||||
export const QUICK_ACTIONS_SCROLL_COLLAPSE_THRESHOLD_PX = 24
|
||||
|
||||
export const QUICK_ACTIONS_SNAP_POINTS = [
|
||||
QUICK_ACTIONS_SNAP_COLLAPSED,
|
||||
QUICK_ACTIONS_SNAP_EXPANDED,
|
||||
QUICK_ACTIONS_SNAP_DOCK,
|
||||
] as const
|
||||
|
||||
export type QuickActionsSnapPoint = (typeof QUICK_ACTIONS_SNAP_POINTS)[number]
|
||||
|
||||
export const quickActionsSnapIsExpanded = (snap: QuickActionsSnapPoint | null): boolean =>
|
||||
snap === QUICK_ACTIONS_SNAP_EXPANDED
|
||||
export const quickActionsSnapIsDock = (snap: QuickActionsSnapPoint | null): boolean =>
|
||||
snap === QUICK_ACTIONS_SNAP_DOCK
|
||||
|
|
|
|||
|
|
@ -406,3 +406,44 @@ test.describe('Municípios — cards no celular (B42)', () => {
|
|||
await expect(page.getByRole('heading', { name: municipality.name })).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* B100: bottom drawer loads docked with search visible, collapses on page scroll,
|
||||
* and reopens only from the handle — not when scrolling back to the top.
|
||||
*/
|
||||
test.describe('Municípios — bottom drawer mobile (B100)', () => {
|
||||
test.use({ viewport: { width: 390, height: 844 } })
|
||||
|
||||
test('dock on load, collapse on scroll down, reopen on handle tap', async ({ campaign, page }) => {
|
||||
const { fixtures } = campaign
|
||||
const coordinator = await fixtures.createCampaignUser('coordinator', {
|
||||
name: fixtures.value('Coordenador Drawer'),
|
||||
})
|
||||
const municipality = await fixtures.claimMunicipality()
|
||||
|
||||
await campaign.login(page, coordinator.email!, coordinator.password)
|
||||
await page.goto(`${campaign.baseURL}/campanha/municipios/${municipality.slug}`)
|
||||
await expect(page.getByRole('heading', { name: municipality.name })).toBeVisible()
|
||||
|
||||
const search = page.getByLabel('Buscar na campanha')
|
||||
await expect(search).toBeInViewport()
|
||||
|
||||
const scrollport = page.locator('[data-slot="campaign-content-scroll"]')
|
||||
await scrollport.evaluate((el) => {
|
||||
el.scrollTop = 80
|
||||
el.dispatchEvent(new Event('scroll'))
|
||||
})
|
||||
|
||||
await expect(search).not.toBeInViewport()
|
||||
await expect(page.getByRole('button', { name: 'Mostrar ações rápidas' })).toBeInViewport()
|
||||
|
||||
await scrollport.evaluate((el) => {
|
||||
el.scrollTop = 0
|
||||
el.dispatchEvent(new Event('scroll'))
|
||||
})
|
||||
await expect(search).not.toBeInViewport()
|
||||
|
||||
await page.getByRole('button', { name: 'Mostrar ações rápidas' }).click()
|
||||
await expect(search).toBeInViewport()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ import {
|
|||
import { resolveQuickActionsForPath } from '@/lib/campaignQuickActionRegistry'
|
||||
import {
|
||||
QUICK_ACTIONS_SNAP_COLLAPSED,
|
||||
QUICK_ACTIONS_SNAP_EXPANDED,
|
||||
quickActionsSnapIsExpanded,
|
||||
QUICK_ACTIONS_SNAP_DOCK,
|
||||
quickActionsSnapIsDock,
|
||||
} from '@/lib/campaignQuickActionSnap'
|
||||
import { resolveOrganizationQuickActions } from '@/lib/organizationQuickActions'
|
||||
|
||||
|
|
@ -395,9 +395,9 @@ describe('campaignQuickActionDemands resolvers', () => {
|
|||
})
|
||||
|
||||
describe('campaignQuickActionSnap', () => {
|
||||
it('detects expanded snap', () => {
|
||||
expect(quickActionsSnapIsExpanded(QUICK_ACTIONS_SNAP_EXPANDED)).toBe(true)
|
||||
expect(quickActionsSnapIsExpanded(QUICK_ACTIONS_SNAP_COLLAPSED)).toBe(false)
|
||||
expect(quickActionsSnapIsExpanded(null)).toBe(false)
|
||||
it('detects dock snap', () => {
|
||||
expect(quickActionsSnapIsDock(QUICK_ACTIONS_SNAP_DOCK)).toBe(true)
|
||||
expect(quickActionsSnapIsDock(QUICK_ACTIONS_SNAP_COLLAPSED)).toBe(false)
|
||||
expect(quickActionsSnapIsDock(null)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,13 +1,31 @@
|
|||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { CampaignGlobalSearchProvider } from '@/components/campaign/dashboard/CampaignGlobalSearchMount'
|
||||
import { CampaignContentScroll } from '@/components/campaign/shell/CampaignQuickActionsHost'
|
||||
import { CampaignQuickActionsDrawer } from '@/components/campaign/shell/CampaignQuickActionsDrawer'
|
||||
import {
|
||||
CampaignQuickActionsScrollCollapse,
|
||||
CampaignQuickActionsSnapProvider,
|
||||
useCampaignQuickActionsSnap,
|
||||
} from '@/components/campaign/shell/CampaignQuickActionsSnapContext'
|
||||
import { postCampaignJson } from '@/lib/campaignJsonRequest'
|
||||
import {
|
||||
QUICK_ACTIONS_SCROLL_COLLAPSE_THRESHOLD_PX,
|
||||
QUICK_ACTIONS_SNAP_COLLAPSED,
|
||||
QUICK_ACTIONS_SNAP_DOCK,
|
||||
quickActionsSnapIsDock,
|
||||
} from '@/lib/campaignQuickActionSnap'
|
||||
|
||||
vi.mock('@/lib/campaignJsonRequest', () => ({
|
||||
postCampaignJson: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
usePathname: () => '/campanha/municipios/foo',
|
||||
}))
|
||||
|
||||
const idleSuggestPayload = {
|
||||
status: 'success' as const,
|
||||
resultKind: 'suggest' as const,
|
||||
|
|
@ -20,29 +38,41 @@ const idleSuggestPayload = {
|
|||
demands: [],
|
||||
}
|
||||
|
||||
const renderQuickActionsChrome = (ui: ReactNode) =>
|
||||
render(
|
||||
<CampaignQuickActionsSnapProvider>
|
||||
<CampaignGlobalSearchProvider>
|
||||
<CampaignQuickActionsScrollCollapse />
|
||||
{ui}
|
||||
</CampaignGlobalSearchProvider>
|
||||
</CampaignQuickActionsSnapProvider>,
|
||||
)
|
||||
|
||||
const SnapReadout = () => {
|
||||
const { snapPoint } = useCampaignQuickActionsSnap()
|
||||
return <div data-testid="snap">{snapPoint ?? 'null'}</div>
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.mocked(postCampaignJson).mockReset()
|
||||
})
|
||||
|
||||
describe('CampaignQuickActionsDrawer (B91)', () => {
|
||||
it('shows global search in the expanded snap', () => {
|
||||
describe('CampaignQuickActionsDrawer (B100)', () => {
|
||||
it('loads in dock snap with global search visible', () => {
|
||||
vi.mocked(postCampaignJson).mockResolvedValue({
|
||||
ok: true,
|
||||
payload: idleSuggestPayload,
|
||||
})
|
||||
|
||||
render(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
expect(screen.queryByLabelText('Buscar na campanha')).toBeNull()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' }))
|
||||
renderQuickActionsChrome(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
const context = document.getElementById('quickActionContext')
|
||||
expect(context?.className).not.toContain('hidden')
|
||||
expect(context?.getAttribute('data-snap')).toBe('expanded')
|
||||
expect(context?.getAttribute('data-snap')).toBe('dock')
|
||||
expect(screen.getByLabelText('Buscar na campanha')).toBeTruthy()
|
||||
expect(screen.getByRole('region', { name: 'Resultados da busca' })).toBeTruthy()
|
||||
expect(screen.getByRole('button', { name: 'Ocultar ações rápidas' })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('posts home-search when the drawer query is active', async () => {
|
||||
|
|
@ -51,9 +81,8 @@ describe('CampaignQuickActionsDrawer (B91)', () => {
|
|||
payload: idleSuggestPayload,
|
||||
})
|
||||
|
||||
render(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
renderQuickActionsChrome(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' }))
|
||||
fireEvent.change(screen.getByLabelText('Buscar na campanha'), { target: { value: 'cairu' } })
|
||||
|
||||
await waitFor(() => {
|
||||
|
|
@ -64,4 +93,63 @@ describe('CampaignQuickActionsDrawer (B91)', () => {
|
|||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('toggles between dock and collapsed from the handle', () => {
|
||||
vi.mocked(postCampaignJson).mockResolvedValue({
|
||||
ok: true,
|
||||
payload: idleSuggestPayload,
|
||||
})
|
||||
|
||||
renderQuickActionsChrome(<CampaignQuickActionsDrawer actions={[]} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Ocultar ações rápidas' }))
|
||||
expect(document.getElementById('quickActionContext')?.getAttribute('data-snap')).toBe(
|
||||
'collapsed',
|
||||
)
|
||||
expect(screen.getByRole('button', { name: 'Mostrar ações rápidas' })).toBeTruthy()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' }))
|
||||
expect(document.getElementById('quickActionContext')?.getAttribute('data-snap')).toBe('dock')
|
||||
})
|
||||
})
|
||||
|
||||
describe('CampaignQuickActionsScrollCollapse (B100)', () => {
|
||||
it('collapses on content scroll down but not when scrolling back to top', () => {
|
||||
renderQuickActionsChrome(
|
||||
<>
|
||||
<CampaignContentScroll quickActionsPeek>
|
||||
<div style={{ height: '2000px' }} />
|
||||
</CampaignContentScroll>
|
||||
<SnapReadout />
|
||||
</>,
|
||||
)
|
||||
|
||||
const scrollport = document.querySelector('[data-slot="campaign-content-scroll"]')
|
||||
expect(scrollport).toBeTruthy()
|
||||
expect(screen.getByTestId('snap').textContent).toBe(QUICK_ACTIONS_SNAP_DOCK)
|
||||
|
||||
Object.defineProperty(scrollport, 'scrollTop', {
|
||||
configurable: true,
|
||||
value: QUICK_ACTIONS_SCROLL_COLLAPSE_THRESHOLD_PX + 1,
|
||||
})
|
||||
fireEvent.scroll(scrollport!)
|
||||
|
||||
expect(screen.getByTestId('snap').textContent).toBe(QUICK_ACTIONS_SNAP_COLLAPSED)
|
||||
|
||||
Object.defineProperty(scrollport, 'scrollTop', {
|
||||
configurable: true,
|
||||
value: 0,
|
||||
})
|
||||
fireEvent.scroll(scrollport!)
|
||||
|
||||
expect(screen.getByTestId('snap').textContent).toBe(QUICK_ACTIONS_SNAP_COLLAPSED)
|
||||
})
|
||||
})
|
||||
|
||||
describe('campaignQuickActionSnap', () => {
|
||||
it('detects dock snap', () => {
|
||||
expect(quickActionsSnapIsDock(QUICK_ACTIONS_SNAP_DOCK)).toBe(true)
|
||||
expect(quickActionsSnapIsDock(QUICK_ACTIONS_SNAP_COLLAPSED)).toBe(false)
|
||||
expect(quickActionsSnapIsDock(null)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue