mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:45:18 +00:00
B122 — Início mobile: ações em grade 2×3 sem scroll
- CampaignHomeActionStrip: variant responsive (grid lt-md, strip md+) - CampaignHomeActionButton: layout strip/grid/responsive class contracts - CampaignHomeLayout: remove mobile bleed for grid gutter - CampaignQuickActionsDrawer: explicit variant=strip - Plans: inicio-acoes-grid-2x3-mobile + impl - Tests: unit grid/strip + e2e mobile visibility Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
This commit is contained in:
parent
13d62bc539
commit
5b6af56b22
10 changed files with 300 additions and 18 deletions
65
docs/plans/inicio-acoes-grid-2x3-mobile-impl.md
Normal file
65
docs/plans/inicio-acoes-grid-2x3-mobile-impl.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# Impl: Início mobile — ações em grade 2×3 (sem scroll)
|
||||
|
||||
Status: aprovado
|
||||
Atualizado em: 2026-08-02
|
||||
Issue: #249
|
||||
Intenção: docs/plans/inicio-acoes-grid-2x3-mobile.md
|
||||
Appetite restante: ~0,5d (layout + tests)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** No Início mobile, as 6 ações staff aparecem numa grade 2×3 sem scroll horizontal; desktop mantém strip.
|
||||
- **O que NÃO negociar:** drawer de outras rotas continua strip; catálogo/lockdown leader; long-press/tooltip.
|
||||
- **O que reavaliar:** bleed `-mx-4` do B99 — removido no mobile porque a grade cabe no gutter.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Strip["CampaignHomeActionStrip"]
|
||||
Strip -->|variant=responsive| R["grid lt-md / flex md+"]
|
||||
Strip -->|variant=strip| S["flex + overflow-x"]
|
||||
```
|
||||
|
||||
**Opções consideradas:** A) CSS só no strip (`grid md:flex`) | B) componente `CampaignHomeActionGrid` | C) duplicar markup no `CampaignHomeActions`
|
||||
**Recomendação:** **A** — edita o dono; `variant` explícito para o drawer.
|
||||
**Rejeitadas:** B (twin); C (duplica map de ações).
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`CampaignHomeActionStrip`** (`src/components/campaign/dashboard/CampaignHomeActionStrip.tsx`): prop `variant?: 'strip' | 'responsive'` (default `'responsive'`). Responsive: `grid grid-cols-2 gap-3` + sem overflow no mobile; `md:flex md:overflow-x-auto` + pan handlers. Strip: comportamento atual.
|
||||
- **`CampaignHomeActionButton`** (`CampaignHomeActionButton.tsx`): `actionControlClassName` ganha `w-full md:w-[5.5rem]` quando pai é responsive (via prop `layout` ou classe compartilhada exportada).
|
||||
- **`CampaignHomeActions`**: passa `variant="responsive"` (explícito).
|
||||
- **`CampaignQuickActionsDrawer`**: passa `variant="strip"`.
|
||||
- **`CampaignHomeLayout`**: `home-actions` perde `-mx-4 w-[calc(100%+2rem)]` no mobile; bleed só se strip voltar (não neste slice).
|
||||
- **Migration:** sem migration.
|
||||
- **UI:** Impeccable B — craft; sem novo token.
|
||||
|
||||
### Dados → forma
|
||||
|
||||
N/A — launcher estático.
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Strip + button** — `variant` + classes grid/strip; drawer `variant="strip"`.
|
||||
2. **Layout + tests** — remover bleed mobile; unit layout/strip; e2e 6 ações visíveis em 390px.
|
||||
3. **Gates** — `pnpm gate:fast`; `pnpm push`.
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- Extrair tile genérico wizard/home.
|
||||
- Grid no drawer.
|
||||
- Mudar tamanho do círculo sem pedido de produto.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Pan handlers em grid mobile:** inofensivos (sem overflow); não montar scroller ref em grid se simplificar.
|
||||
- **Teste bleed antigo:** atualizar spec `campaignHomeLayout` para refletir gutter no mobile.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [x] Aceite de produto da intenção ainda coberto
|
||||
- [x] Invariantes AGENTS/engineering-standards
|
||||
- [x] Testes de domínio previstos (unit/int) onde access/write paths mudam
|
||||
|
||||
Self-score decision-quality: 5/5
|
||||
123
docs/plans/inicio-acoes-grid-2x3-mobile.md
Normal file
123
docs/plans/inicio-acoes-grid-2x3-mobile.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Início mobile — ações em grade 2×3 (sem scroll)
|
||||
|
||||
Status: registrado
|
||||
Atualizado em: 2026-08-02
|
||||
Issue: #249
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — `CampaignHomeActionStrip` / `CampaignHomeLayout` (Início staff/leader)
|
||||
Appetite: ~0,5d eng; layout responsivo; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Clarity under pressure; Feel the action) / `DESIGN.md` · B44/B45 strip · B63 grid 2×3 wizard · tema `campaign`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish (iPhone 390px; 6 ações staff).
|
||||
|
||||
Brief:
|
||||
|
||||
- **Persona / contexto:** CG/assessor no celular — polegar na thumb zone; hoje precisa pan horizontal para ver as 6 ações.
|
||||
- **Job principal:** ver **todas** as ações staff (6) de uma vez, sem scroll horizontal, em grade 2 colunas × 3 linhas.
|
||||
- **Estratégia de cor:** herda B44 (círculo `bg-muted` + label).
|
||||
- **Edit where you see:** não — launchers.
|
||||
- **Anti-goals:** card com borda envolvendo a grade; scroll horizontal no mobile do Início; mudar o drawer de outras rotas (continua strip).
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ /campanha mobile (< md) ──────────────────────┐
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Ajustar │ │Registrar │ │
|
||||
│ │ votos │ │ sinal │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Mudar │ │Atualizar │ │
|
||||
│ │tendência │ │liderança │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │Registrar │ │ Ver │ │
|
||||
│ │ pedido │ │esquecidos│ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ ┌ Buscar na campanha ────────────────────────┐│
|
||||
└────────────────────────────────────────────────┘
|
||||
|
||||
md+: strip horizontal atual (B44/B67) — inalterada.
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — chrome de launcher; catálogo B45 inalterado.
|
||||
|
||||
## Contexto
|
||||
|
||||
**B44–B74 / B99** iteraram na strip horizontal (gap, bleed, pan). Com 6 ações staff, o mobile ainda exige pan — produto pede **grade 2×3 sem scroll** no Início. Precedente visual: `WizardSignalTypeStep` (`grid-cols-2 gap-3`). O **drawer** de ações rápidas (B105) permanece strip horizontal no peek.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- **< `md` (Início):** `CampaignHomeActions` renderiza grade `grid-cols-2 gap-3`; 6 ações staff visíveis sem `overflow-x`; liderança (2 ações) em 1 linha.
|
||||
- **`md+`:** strip horizontal atual (scroll, pan fine, tooltip).
|
||||
- `CampaignQuickActionsDrawer`: continua `variant="strip"` (sem grade).
|
||||
- Remover bleed `-mx-4` do slot `home-actions` no mobile (grade respeita gutter `p-4`).
|
||||
- Long-press Drawer / Tooltip fine inalterados.
|
||||
- Unit + e2e smoke: grade no mobile; strip no drawer; ações staff todas visíveis em viewport 390.
|
||||
- Sem migration / collection / server action.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Grade só no Início mobile; strip no drawer e no `md+`.** **Rejeitado:** grade global (drawer peek não cabe 3 linhas); manter strip no mobile “com gap menor” (B99/B74 esgotaram).
|
||||
- **`variant` no dono `CampaignHomeActionStrip`.** `responsive` (default no Início) vs `strip` (drawer). **Rejeitado:** componente paralelo `CampaignHomeActionGrid`.
|
||||
- **Botão com classes responsivas** (`w-full md:w-[5.5rem]`). **Rejeitado:** segundo primitivo de botão.
|
||||
- **i18n:** ids/classes em inglês; copy B58 intacta.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Círculo menor na grade?** **Opções:** A) manter `size-14` | B) `size-12` na grade. **Recomendação:** **A** — célula larga o suficiente; critique mede. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Home["CampaignHomeActions"] --> Strip["CampaignHomeActionStrip variant=responsive"]
|
||||
Drawer["CampaignQuickActionsDrawer"] --> Strip2["CampaignHomeActionStrip variant=strip"]
|
||||
Strip --> Grid["lt md: grid 2×3"]
|
||||
Strip --> HStrip["md+: horizontal strip"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`CampaignHomeActionStrip.tsx`:** prop `variant`; lista `grid grid-cols-2 gap-3` vs `flex` + overflow.
|
||||
- **`CampaignHomeActionButton.tsx`:** `w-full` na grade / largura fixa na strip.
|
||||
- **`CampaignHomeLayout.tsx`:** remover bleed mobile do slot `home-actions`.
|
||||
- **Testes:** unit strip/grid classes; e2e 6 ações visíveis em 390px.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Dura: B45 ✓ (catálogo), B44 ✓ (primitivo). Soft: B99 bleed (revertido no mobile para grade).
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Catálogo / labels / wizards.
|
||||
- Grade no drawer B105.
|
||||
- Mudar ordem thumb-zone (B46).
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Unificar grade wizard + home num tile genérico.** Mitigação: classes Tailwind compartilhadas só; sem extrair até 3º call site.
|
||||
- **CSS container queries.** Mitigação: breakpoint `md` como o resto do Início.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Grade no drawer** se peek crescer. Revisitar com redesign B105.
|
||||
- **3 colunas em tablet portrait.** Revisitar se `md` grid pedido.
|
||||
|
||||
## Referências
|
||||
|
||||
- Issue #249 · `WizardSignalTypeStep.tsx` (grid 2×3)
|
||||
- `CampaignHomeActionStrip.tsx` · `CampaignHomeLayout.tsx` · `CampaignQuickActionsDrawer.tsx`
|
||||
- [botao-acao-inicio-strip.md](botao-acao-inicio-strip.md) · [strip-acoes-edge-gap-inicio.md](strip-acoes-edge-gap-inicio.md)
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
|
||||
Qualidade de decisão: 5/5
|
||||
|
|
@ -25,11 +25,21 @@ export type CampaignHomeActionButtonProps = {
|
|||
href?: string
|
||||
onClick?: () => void
|
||||
disabled?: boolean
|
||||
layout?: 'strip' | 'grid' | 'responsive'
|
||||
}
|
||||
|
||||
export const actionControlClassName =
|
||||
export const actionStripControlClassName =
|
||||
'group box-content flex min-h-11 w-[5.5rem] 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 actionGridControlClassName =
|
||||
'group box-content flex min-h-11 w-full 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 actionResponsiveControlClassName =
|
||||
'group box-content flex min-h-11 w-full 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 md:w-[5.5rem] md:shrink-0 md:snap-start'
|
||||
|
||||
/** @deprecated Use actionStripControlClassName or actionGridControlClassName */
|
||||
export const actionControlClassName = actionStripControlClassName
|
||||
|
||||
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'
|
||||
|
||||
|
|
@ -51,6 +61,7 @@ export const CampaignHomeActionButton = ({
|
|||
href,
|
||||
onClick,
|
||||
disabled = false,
|
||||
layout = 'strip',
|
||||
}: CampaignHomeActionButtonProps) => {
|
||||
const isCoarsePointer = useCoarsePointer()
|
||||
const [drawerOpen, setDrawerOpen] = useState(false)
|
||||
|
|
@ -74,7 +85,14 @@ export const CampaignHomeActionButton = ({
|
|||
},
|
||||
})
|
||||
|
||||
const className = cn(actionControlClassName, isInert && 'cursor-default')
|
||||
const className = cn(
|
||||
layout === 'grid'
|
||||
? actionGridControlClassName
|
||||
: layout === 'responsive'
|
||||
? actionResponsiveControlClassName
|
||||
: actionStripControlClassName,
|
||||
isInert && 'cursor-default',
|
||||
)
|
||||
const aria = {
|
||||
'aria-label': label,
|
||||
'aria-describedby': drawerOpen && longPressEnabled ? descriptionId : undefined,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import { cn } from '@/lib/utils'
|
|||
/** Horizontal pan threshold (px) — below this, child link/button clicks still fire. */
|
||||
export const HOME_ACTION_STRIP_DRAG_THRESHOLD_PX = 8
|
||||
|
||||
export type CampaignHomeActionStripVariant = 'strip' | 'responsive'
|
||||
|
||||
type DragSession = {
|
||||
pointerId: number
|
||||
startX: number
|
||||
|
|
@ -33,15 +35,19 @@ export const CampaignHomeActionStrip = ({
|
|||
actions,
|
||||
ariaLabel = 'Ações rápidas',
|
||||
className,
|
||||
variant = 'responsive',
|
||||
}: {
|
||||
actions?: readonly (CampaignHomeActionButtonProps & { id?: string })[]
|
||||
ariaLabel?: string
|
||||
className?: string
|
||||
/** `responsive`: 2×3 grid on mobile, horizontal strip on md+. `strip`: always horizontal. */
|
||||
variant?: CampaignHomeActionStripVariant
|
||||
}) => {
|
||||
const isCoarsePointer = useCoarsePointer()
|
||||
const scrollerRef = useRef<HTMLDivElement>(null)
|
||||
const dragRef = useRef<DragSession | null>(null)
|
||||
const suppressClickRef = useRef(false)
|
||||
const isStripLayout = variant === 'strip'
|
||||
|
||||
const endDragSession = useCallback((pointerId: number) => {
|
||||
const session = dragRef.current
|
||||
|
|
@ -126,25 +132,36 @@ export const CampaignHomeActionStrip = ({
|
|||
onClickCapture,
|
||||
}
|
||||
|
||||
const listClassName = cn(
|
||||
'm-0 list-none p-0',
|
||||
isStripLayout
|
||||
? 'flex min-w-max snap-x snap-proximity gap-0 px-4 pb-1 md:px-0'
|
||||
: 'grid grid-cols-2 gap-3 md:flex md:min-w-max md:snap-x md:snap-proximity md:gap-0 md:px-0 md:pb-1',
|
||||
)
|
||||
|
||||
const scrollerClassName = cn(
|
||||
'min-w-0',
|
||||
isStripLayout
|
||||
? 'overflow-x-auto overflow-y-hidden overscroll-x-contain [touch-action:pan-x] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden pointer-fine:cursor-grab pointer-fine:data-[dragging=true]:cursor-grabbing pointer-fine:data-[dragging=true]:select-none'
|
||||
: 'md:overflow-x-auto md:overflow-y-hidden md:overscroll-x-contain md:[touch-action:pan-x] md:[scrollbar-width:none] md:[&::-webkit-scrollbar]:hidden md:pointer-fine:cursor-grab md:pointer-fine:data-[dragging=true]:cursor-grabbing md:pointer-fine:data-[dragging=true]:select-none',
|
||||
className,
|
||||
)
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<div
|
||||
ref={scrollerRef}
|
||||
aria-label={ariaLabel}
|
||||
className={cn(
|
||||
'min-w-0 overflow-x-auto overflow-y-hidden overscroll-x-contain [touch-action:pan-x] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
|
||||
'pointer-fine:cursor-grab pointer-fine:data-[dragging=true]:cursor-grabbing pointer-fine:data-[dragging=true]:select-none',
|
||||
className,
|
||||
)}
|
||||
className={scrollerClassName}
|
||||
{...finePointerPanProps}
|
||||
>
|
||||
<ul
|
||||
role="list"
|
||||
className="m-0 flex min-w-max list-none snap-x snap-proximity gap-0 px-4 pb-1 md:px-0"
|
||||
>
|
||||
<ul role="list" className={listClassName}>
|
||||
{actions?.map(({ id, ...button }) => (
|
||||
<li key={id ?? button.href ?? button.label} className="m-0 list-none p-0">
|
||||
<CampaignHomeActionButton {...button} />
|
||||
<CampaignHomeActionButton
|
||||
{...button}
|
||||
layout={isStripLayout ? 'strip' : 'responsive'}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -19,5 +19,5 @@ export const CampaignHomeActions = ({
|
|||
|
||||
if (actions.length === 0) return null
|
||||
|
||||
return <CampaignHomeActionStrip actions={actions} className="w-full" />
|
||||
return <CampaignHomeActionStrip actions={actions} className="w-full" variant="responsive" />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export const CampaignHomeLayout = ({
|
|||
>
|
||||
<div
|
||||
data-slot="home-actions"
|
||||
className="min-w-0 -mx-4 w-[calc(100%+2rem)] md:mx-0 md:w-auto"
|
||||
className="min-w-0 md:mx-0 md:w-auto"
|
||||
>
|
||||
{actions}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ export const CampaignQuickActionsDrawer = ({
|
|||
{showActions ? (
|
||||
<div className="-mx-4 w-[calc(100%+2rem)]">
|
||||
<CampaignHomeActionStrip
|
||||
variant="strip"
|
||||
actions={actions.map((action) => ({
|
||||
id: action.id,
|
||||
label: action.label,
|
||||
|
|
|
|||
|
|
@ -102,6 +102,32 @@ test.describe('Início — busca global (B47)', () => {
|
|||
})
|
||||
|
||||
test.describe('Início — catálogo de ações (B45)', () => {
|
||||
test.use({ viewport: { width: 390, height: 844 } })
|
||||
|
||||
test('staff sees all six home actions in the mobile grid without horizontal scroll', async ({
|
||||
campaign,
|
||||
page,
|
||||
}) => {
|
||||
const { fixtures } = campaign
|
||||
const coordinator = await fixtures.createCampaignUser('coordinator', {
|
||||
name: fixtures.value('Coordenadora Geral'),
|
||||
})
|
||||
|
||||
await campaign.login(page, coordinator.email!, coordinator.password)
|
||||
const actionsRegion = page.getByLabel('Ações rápidas')
|
||||
await expect(actionsRegion).toBeVisible()
|
||||
|
||||
for (const label of staffActionLabels) {
|
||||
await expect(page.getByRole('link', { name: label, exact: true }).or(
|
||||
page.getByRole('button', { name: label, exact: true }),
|
||||
)).toBeVisible()
|
||||
}
|
||||
|
||||
const overflowX = await actionsRegion.evaluate((el) => getComputedStyle(el).overflowX)
|
||||
expect(overflowX).not.toBe('auto')
|
||||
expect(overflowX).not.toBe('scroll')
|
||||
})
|
||||
|
||||
test('staff sees six home actions and can open municipalities without coverage', async ({
|
||||
campaign,
|
||||
page,
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ describe('CampaignHomeActionStrip', () => {
|
|||
it('renders a horizontal scroller with list semantics', () => {
|
||||
const { container } = render(
|
||||
<CampaignHomeActionStrip
|
||||
variant="strip"
|
||||
actions={[
|
||||
{ label: 'Um', icon: BarChart3 },
|
||||
{ label: 'Dois', icon: BarChart3 },
|
||||
|
|
@ -189,10 +190,34 @@ describe('CampaignHomeActionStrip', () => {
|
|||
expect(scroller).toBeTruthy()
|
||||
const list = scroller?.querySelector('ul[role="list"]')
|
||||
expect(list).toBeTruthy()
|
||||
expect(list?.className).toMatch(/flex/)
|
||||
})
|
||||
|
||||
it('renders a 2-column grid on mobile when variant is responsive', () => {
|
||||
const { container } = render(
|
||||
<CampaignHomeActionStrip
|
||||
variant="responsive"
|
||||
actions={[
|
||||
{ label: 'Um', icon: BarChart3 },
|
||||
{ label: 'Dois', icon: BarChart3 },
|
||||
]}
|
||||
/>,
|
||||
)
|
||||
|
||||
const region = container.querySelector('[aria-label="Ações rápidas"]')
|
||||
expect(region).toBeTruthy()
|
||||
const list = region?.querySelector('ul[role="list"]')
|
||||
expect(list).toBeTruthy()
|
||||
expect(list?.className).toMatch(/grid-cols-2/)
|
||||
expect(list?.className).toMatch(/md:flex/)
|
||||
expect(region?.className).not.toMatch(/overflow-x-auto/)
|
||||
expect(region?.className).toMatch(/md:overflow-x-auto/)
|
||||
})
|
||||
|
||||
it('scrolls horizontally on pointer-fine drag past the threshold', () => {
|
||||
const { container } = render(<CampaignHomeActionStrip actions={manyActions} />)
|
||||
const { container } = render(
|
||||
<CampaignHomeActionStrip variant="strip" actions={manyActions} />,
|
||||
)
|
||||
const scroller = getScroller(container)
|
||||
mockScrollerOverflow(scroller)
|
||||
|
||||
|
|
@ -227,6 +252,7 @@ describe('CampaignHomeActionStrip', () => {
|
|||
const onClick = vi.fn()
|
||||
const { container } = render(
|
||||
<CampaignHomeActionStrip
|
||||
variant="strip"
|
||||
actions={[{ label: 'Registrar', icon: BarChart3, onClick }, ...manyActions]}
|
||||
/>,
|
||||
)
|
||||
|
|
@ -246,6 +272,7 @@ describe('CampaignHomeActionStrip', () => {
|
|||
const onClick = vi.fn()
|
||||
const { container } = render(
|
||||
<CampaignHomeActionStrip
|
||||
variant="strip"
|
||||
actions={[{ label: 'Registrar', icon: BarChart3, onClick }, ...manyActions.slice(0, 3)]}
|
||||
/>,
|
||||
)
|
||||
|
|
@ -263,7 +290,10 @@ describe('CampaignHomeActionStrip', () => {
|
|||
it('allows child click when movement stays below the drag threshold', () => {
|
||||
const onClick = vi.fn()
|
||||
const { container } = render(
|
||||
<CampaignHomeActionStrip actions={[{ label: 'Registrar', icon: BarChart3, onClick }]} />,
|
||||
<CampaignHomeActionStrip
|
||||
variant="strip"
|
||||
actions={[{ label: 'Registrar', icon: BarChart3, onClick }]}
|
||||
/>,
|
||||
)
|
||||
const scroller = getScroller(container)
|
||||
mockScrollerOverflow(scroller)
|
||||
|
|
@ -285,6 +315,7 @@ describe('CampaignHomeActionStrip', () => {
|
|||
const onLinkClick = vi.fn()
|
||||
const { container } = render(
|
||||
<CampaignHomeActionStrip
|
||||
variant="strip"
|
||||
actions={[
|
||||
{
|
||||
label: 'Ajustar votos',
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@ describe('CampaignHomeLayout', () => {
|
|||
expect(actions?.closest('[data-slot="home-dock"]')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('bleeds home-actions edge-to-edge on mobile (compensates scroll p-4)', () => {
|
||||
it('keeps home-actions within page gutter on mobile (B122 grid)', () => {
|
||||
const { container } = render(<CampaignHomeLayout actions={<p>Actions block</p>} />)
|
||||
|
||||
const actions = container.querySelector('[data-slot="home-actions"]')
|
||||
expect(actions).not.toBeNull()
|
||||
expect(actions?.closest('[data-slot="home-actions-chrome"]')).not.toBeNull()
|
||||
expect(actions?.className).not.toMatch(/-mx-4/)
|
||||
expect(actions?.className).not.toMatch(/calc\(100%\+2rem\)/)
|
||||
})
|
||||
|
||||
it('does not clip horizontal bleed on the actions retraction shell', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue