diff --git a/src/components/campaign/dashboard/CampaignGlobalSearchMount.tsx b/src/components/campaign/dashboard/CampaignGlobalSearchMount.tsx
new file mode 100644
index 00000000..04a53dba
--- /dev/null
+++ b/src/components/campaign/dashboard/CampaignGlobalSearchMount.tsx
@@ -0,0 +1,23 @@
+'use client'
+
+import type { ReactNode } from 'react'
+
+import { CampaignStaffGlobalSearchBody } from '@/components/campaign/dashboard/CampaignStaffGlobalSearch'
+import { HomeSearchProvider } from '@/components/campaign/dashboard/HomeSearchContext'
+import { useHomeSearchQuery } from '@/components/campaign/dashboard/useHomeSearchQuery'
+
+/** Owns debounce + focus state for any global search surface (Início, drawer B91). */
+export const CampaignGlobalSearchProvider = ({ children }: { children: ReactNode }) => {
+ const searchState = useHomeSearchQuery()
+ return {children}
+}
+
+/** Input + result groups — requires `CampaignGlobalSearchProvider` unless using the mount. */
+export const CampaignGlobalSearchBody = CampaignStaffGlobalSearchBody
+
+/** Provider + default global search body (drawer, smoke tests). */
+export const CampaignGlobalSearchMount = () => (
+
+
+
+)
diff --git a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx
index ab17d8ec..41e3d69c 100644
--- a/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx
+++ b/src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx
@@ -2,15 +2,17 @@
import type { ReactNode } from 'react'
+import {
+ CampaignGlobalSearchBody,
+ CampaignGlobalSearchProvider,
+} from '@/components/campaign/dashboard/CampaignGlobalSearchMount'
import { CampaignHomeLayout } from '@/components/campaign/dashboard/CampaignHomeLayout'
import { CampaignHomeSearch } from '@/components/campaign/dashboard/CampaignHomeSearch'
-import { CampaignStaffGlobalSearchBody } from '@/components/campaign/dashboard/CampaignStaffGlobalSearch'
-import { HomeSearchProvider } from '@/components/campaign/dashboard/HomeSearchContext'
+import { useHomeSearch } from '@/components/campaign/dashboard/HomeSearchContext'
import {
HomeSearchResultsProvider,
useHomeSearchResultsState,
} from '@/components/campaign/dashboard/HomeSearchResultsContext'
-import { useHomeSearchQuery } from '@/components/campaign/dashboard/useHomeSearchQuery'
const CampaignHomeStaffSearchSlot = ({ searchResults }: { searchResults: ReactNode }) => {
const searchResultsState = useHomeSearchResultsState()
@@ -24,7 +26,7 @@ const CampaignHomeStaffSearchSlot = ({ searchResults }: { searchResults: ReactNo
)
}
-export const CampaignHomeStaffChrome = ({
+const CampaignHomeStaffChromeInner = ({
actions,
searchResults,
summarySlot,
@@ -34,25 +36,40 @@ export const CampaignHomeStaffChrome = ({
searchResults?: ReactNode
summarySlot?: ReactNode
}) => {
- const searchState = useHomeSearchQuery()
- const focused = searchState.uiFocused
+ const { uiFocused: focused } = useHomeSearch()
return (
-
-
-
- ) : (
-
- )
- }
- summarySlot={summarySlot}
- />
-
-
+
+
+ ) : (
+
+ )
+ }
+ summarySlot={summarySlot}
+ />
+
)
}
+
+export const CampaignHomeStaffChrome = ({
+ actions,
+ searchResults,
+ summarySlot,
+}: {
+ actions: ReactNode
+ searchResults?: ReactNode
+ summarySlot?: ReactNode
+}) => (
+
+
+
+)
diff --git a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx
index 2e137d2d..c5af7831 100644
--- a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx
+++ b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx
@@ -2,6 +2,7 @@
import { useCallback, useState } from 'react'
+import { CampaignGlobalSearchMount } from '@/components/campaign/dashboard/CampaignGlobalSearchMount'
import { CampaignHomeActionStrip } from '@/components/campaign/dashboard/CampaignHomeActionStrip'
import { Drawer, DrawerContent, DrawerTitle } from '@/components/ui/Drawer'
import {
@@ -103,6 +104,7 @@ export const CampaignQuickActionsDrawer = ({
) : (
Nenhuma ação rápida nesta página.
)}
+ {expanded ? : null}
diff --git a/src/lib/campaignQuickActionRegistry.ts b/src/lib/campaignQuickActionRegistry.ts
index 3c875182..82f3b170 100644
--- a/src/lib/campaignQuickActionRegistry.ts
+++ b/src/lib/campaignQuickActionRegistry.ts
@@ -3,13 +3,13 @@ import { resolveStaffHomeQuickActions } from '@/lib/campaignHomeActions'
import { resolveMunicipalityQuickActionsForPath } from '@/lib/campaignMunicipalityQuickActions'
import { CAMPAIGN_TERRITORIES_HOME } from '@/lib/campaignPaths'
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
-import { isLeaderContactsPath } from '@/lib/campaignQuickActionMount'
import {
isDemandDetailPath,
isDemandsListPath,
resolveDemandDetailQuickActions,
resolveDemandsListQuickActions,
} from '@/lib/campaignQuickActionDemands'
+import { isLeaderContactsPath } from '@/lib/campaignQuickActionMount'
import {
parseActivityQuickActionSurface,
parseOrganizationQuickActionSurface,
diff --git a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
new file mode 100644
index 00000000..8481f257
--- /dev/null
+++ b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
@@ -0,0 +1,67 @@
+import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
+import { afterEach, describe, expect, it, vi } from 'vitest'
+
+import { CampaignQuickActionsDrawer } from '@/components/campaign/shell/CampaignQuickActionsDrawer'
+import { postCampaignJson } from '@/lib/campaignJsonRequest'
+
+vi.mock('@/lib/campaignJsonRequest', () => ({
+ postCampaignJson: vi.fn(),
+}))
+
+const idleSuggestPayload = {
+ status: 'success' as const,
+ resultKind: 'suggest' as const,
+ municipalities: [],
+ territories: [],
+ advisors: [],
+ leaderships: [],
+ stateDeputies: [],
+ activities: [],
+ demands: [],
+}
+
+afterEach(() => {
+ cleanup()
+ vi.mocked(postCampaignJson).mockReset()
+})
+
+describe('CampaignQuickActionsDrawer (B91)', () => {
+ it('shows global search in the expanded snap', () => {
+ vi.mocked(postCampaignJson).mockResolvedValue({
+ ok: true,
+ payload: idleSuggestPayload,
+ })
+
+ render()
+
+ expect(screen.queryByLabelText('Buscar na campanha')).toBeNull()
+
+ fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' }))
+
+ const context = document.getElementById('quickActionContext')
+ expect(context?.className).not.toContain('hidden')
+ expect(context?.getAttribute('data-snap')).toBe('expanded')
+ expect(screen.getByLabelText('Buscar na campanha')).toBeTruthy()
+ expect(screen.getByRole('region', { name: 'Resultados da busca' })).toBeTruthy()
+ })
+
+ it('posts home-search when the drawer query is active', async () => {
+ vi.mocked(postCampaignJson).mockResolvedValue({
+ ok: true,
+ payload: idleSuggestPayload,
+ })
+
+ render()
+
+ fireEvent.click(screen.getByRole('button', { name: 'Mostrar ações rápidas' }))
+ fireEvent.change(screen.getByLabelText('Buscar na campanha'), { target: { value: 'cairu' } })
+
+ await waitFor(() => {
+ expect(postCampaignJson).toHaveBeenCalledWith(
+ '/campanha/home-search',
+ { mode: 'search', query: 'cairu' },
+ expect.any(AbortSignal),
+ )
+ })
+ })
+})