diff --git a/src/components/campaign/dashboard/CampaignHomeActionButton.tsx b/src/components/campaign/dashboard/CampaignHomeActionButton.tsx
index 43505bfa..f65082ca 100644
--- a/src/components/campaign/dashboard/CampaignHomeActionButton.tsx
+++ b/src/components/campaign/dashboard/CampaignHomeActionButton.tsx
@@ -27,8 +27,8 @@ export type CampaignHomeActionButtonProps = {
disabled?: boolean
}
-const actionControlClassName =
- 'group flex min-h-11 w-[4.75rem] shrink-0 snap-start flex-col items-center gap-2 rounded-md text-center outline-none focus-visible:ring-2 focus-visible:ring-ring active:opacity-90 disabled:pointer-events-none disabled:opacity-50'
+export const actionControlClassName =
+ 'group box-content flex min-h-11 w-[4.75rem] shrink-0 snap-start flex-col items-center gap-2 rounded-md px-2 text-center outline-none focus-visible:ring-2 focus-visible:ring-ring active:opacity-90 disabled:pointer-events-none disabled:opacity-50'
const circleClassName =
'flex size-14 items-center justify-center rounded-full bg-muted text-foreground transition-[transform,colors] duration-150 ease-out motion-reduce:transition-none pointer-fine:group-hover:scale-[1.05] pointer-coarse:group-data-[pressing=true]:scale-[1.05] motion-reduce:scale-100 group-hover:bg-muted/80 group-focus-visible:bg-muted/80'
diff --git a/src/components/campaign/dashboard/CampaignHomeActionStrip.tsx b/src/components/campaign/dashboard/CampaignHomeActionStrip.tsx
index 14242a25..35f41959 100644
--- a/src/components/campaign/dashboard/CampaignHomeActionStrip.tsx
+++ b/src/components/campaign/dashboard/CampaignHomeActionStrip.tsx
@@ -140,7 +140,7 @@ export const CampaignHomeActionStrip = ({
>
{actions?.map(({ id, ...button }) => (
-
diff --git a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx
index 306a59c1..fa34ebe3 100644
--- a/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx
+++ b/src/components/campaign/shell/CampaignQuickActionsDrawer.tsx
@@ -78,16 +78,18 @@ export const CampaignQuickActionsDrawer = ({
aria-hidden={!isDock}
>
{showActions ? (
- ({
- id: action.id,
- label: action.label,
- icon: action.icon,
- description: action.description,
- href: action.href,
- }))}
- className="w-full"
- />
+
+ ({
+ id: action.id,
+ label: action.label,
+ icon: action.icon,
+ description: action.description,
+ href: action.href,
+ }))}
+ className="w-full"
+ />
+
) : null}
diff --git a/tests/unit/campaignHomeActionButton.unit.spec.tsx b/tests/unit/campaignHomeActionButton.unit.spec.tsx
index edcc4d3c..2646d024 100644
--- a/tests/unit/campaignHomeActionButton.unit.spec.tsx
+++ b/tests/unit/campaignHomeActionButton.unit.spec.tsx
@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
CampaignHomeActionButton,
+ actionControlClassName,
type CampaignHomeActionButtonProps,
} from '@/components/campaign/dashboard/CampaignHomeActionButton'
import {
@@ -90,6 +91,16 @@ describe('CampaignHomeActionButton', () => {
expect(screen.getByLabelText('Em breve').getAttribute('aria-disabled')).toBe('true')
})
+ it('uses horizontal padding for hit area without shrinking the visual column (B101)', () => {
+ renderActionButton({ label: 'Registrar', icon: BarChart3 })
+ const control = screen.getByRole('button', { name: 'Registrar' })
+ expect(control.className).toContain('px-2')
+ expect(control.className).toContain('box-content')
+ expect(control.className).toContain('w-[4.75rem]')
+ expect(actionControlClassName).toContain('px-2')
+ expect(actionControlClassName).toContain('box-content')
+ })
+
it('opens the description drawer on long-press when the pointer is coarse', () => {
resetCampaignCoarsePointerForTests()
matchMediaMock.mockImplementation((query: string) => ({
@@ -180,7 +191,12 @@ describe('CampaignHomeActionStrip', () => {
const scroller = getScroller(container)
expect(scroller?.className).toContain('overflow-x-auto')
expect(scroller?.className).toContain('scrollbar-width:none')
- expect(scroller?.querySelector('ul[role="list"]')).toBeTruthy()
+ const list = scroller?.querySelector('ul[role="list"]')
+ expect(list).toBeTruthy()
+ expect(list?.className).toContain('gap-0')
+ expect(list?.className).toContain('px-4')
+ expect(list?.className).toContain('md:px-0')
+ expect(list?.className).not.toContain('gap-2')
})
it('scrolls horizontally on pointer-fine drag past the threshold', () => {
diff --git a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
index 663cf86c..2ff36fa9 100644
--- a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
+++ b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx
@@ -1,6 +1,7 @@
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
+import { BarChart3 } from 'lucide-react'
import type { ReactNode } from 'react'
-import { afterEach, describe, expect, it, vi } from 'vitest'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { CampaignGlobalSearchProvider } from '@/components/campaign/dashboard/CampaignGlobalSearchMount'
import { CampaignQuickActionsDrawer } from '@/components/campaign/shell/CampaignQuickActionsDrawer'
@@ -37,6 +38,27 @@ const idleSuggestPayload = {
demands: [],
}
+const matchMediaMock = vi.fn()
+
+beforeEach(() => {
+ matchMediaMock.mockImplementation((query: string) => ({
+ matches: false,
+ media: query,
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ addListener: vi.fn(),
+ removeListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ }))
+ vi.stubGlobal('matchMedia', matchMediaMock)
+})
+
+afterEach(() => {
+ cleanup()
+ vi.mocked(postCampaignJson).mockReset()
+ vi.unstubAllGlobals()
+})
+
const renderQuickActionsChrome = (ui: ReactNode) =>
render(
@@ -49,11 +71,6 @@ const SnapReadout = () => {
return {snapPoint ?? 'null'}
}
-afterEach(() => {
- cleanup()
- vi.mocked(postCampaignJson).mockReset()
-})
-
describe('CampaignQuickActionsDrawer (B100)', () => {
it('loads in dock snap with global search visible', () => {
vi.mocked(postCampaignJson).mockResolvedValue({
@@ -71,6 +88,33 @@ describe('CampaignQuickActionsDrawer (B100)', () => {
expect(screen.getByRole('button', { name: 'Ocultar ações rápidas' })).toBeTruthy()
})
+ it('bleeds the action strip edge-to-edge inside the drawer gutter (B101)', () => {
+ vi.mocked(postCampaignJson).mockResolvedValue({
+ ok: true,
+ payload: idleSuggestPayload,
+ })
+
+ renderQuickActionsChrome(
+ ,
+ )
+
+ const context = document.getElementById('quickActionContext')
+ const stripBleed = context?.firstElementChild
+ expect(stripBleed?.className).toContain('-mx-4')
+ expect(stripBleed?.className).toContain('w-[calc(100%+2rem)]')
+ expect(stripBleed?.querySelector('[aria-label="Ações rápidas"]')).not.toBeNull()
+ })
+
it('posts home-search when the drawer query is active', async () => {
vi.mocked(postCampaignJson).mockResolvedValue({
ok: true,