Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@
## 2026-01-22 - Replacing Text Characters with Semantic Icons
**Learning:** In `AppSidebar.tsx`, a text character "â–¼" was used for the expand/collapse arrow. Screen readers might announce this as "Black Down-Pointing Triangle" or similar, which is distracting. Text characters also don't scale or style as consistently as SVGs.
**Action:** Replace decorative text characters with `CyberIcon` (or proper SVG icons) and add `aria-hidden="true"` to ensure they are treated as visual decoration only.

## 2026-05-24 - Interactive List Items as Buttons
**Learning:** In `GlobalSearchModal.tsx`, search results were implemented as clickable `div`s, making them inaccessible to keyboard users (no tab focus). Also, `Dialog` components from `@radix-ui` require a `DialogDescription` for proper accessibility, even if visually hidden.
**Action:** Always use `<button>` for interactive list items (adding `w-full text-left` to preserve layout) and ensure every `Dialog` has a `DialogDescription` (using `sr-only` if necessary).
12 changes: 8 additions & 4 deletions src/components/GlobalSearchModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Componente de busca global para todo o sistema
import { Badge } from "@/components/ui/badge";
import { CyberIcon } from '@/components/ui/cyber-icons';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { apiClient } from "@/services/api-client";
import { formatChatDate } from "@/utils/chatUtils";
Expand Down Expand Up @@ -231,6 +231,9 @@ export const GlobalSearchModal = ({ isOpen, onClose }: GlobalSearchModalProps) =
<CyberIcon name="search" className="w-5 h-5" />
Busca Global
</DialogTitle>
<DialogDescription className="sr-only">
Busque por conversas, documentos, automações e páginas do sistema
</DialogDescription>
</DialogHeader>

<div className="space-y-4">
Expand Down Expand Up @@ -261,10 +264,11 @@ export const GlobalSearchModal = ({ isOpen, onClose }: GlobalSearchModalProps) =
)}

{searchResults.map((result) => (
<div
<button
type="button"
key={`${result.type}-${result.id}`}
onClick={() => handleSelectResult(result)}
className="p-3 bg-black/40 rounded-lg border border-violet-500/20 hover:border-violet-500/50 cursor-pointer transition-all hover:bg-black/60"
className="w-full text-left p-3 bg-black/40 rounded-lg border border-violet-500/20 hover:border-violet-500/50 transition-all hover:bg-black/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500"
>
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
Expand Down Expand Up @@ -293,7 +297,7 @@ export const GlobalSearchModal = ({ isOpen, onClose }: GlobalSearchModalProps) =
</div>
</div>
</div>
</div>
</button>
))}
</>
) : searchQuery.trim() ? (
Expand Down
36 changes: 36 additions & 0 deletions src/tests/components/GlobalSearchModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { render, screen } from '@testing-library/react';
import { GlobalSearchModal } from '../../components/GlobalSearchModal';
import { MemoryRouter } from 'react-router-dom';
import { vi } from 'vitest';

// Mock apiClient
vi.mock('@/services/api-client', () => ({
default: {
get: vi.fn().mockResolvedValue({ success: true, data: [] }),
},
apiClient: {
get: vi.fn().mockResolvedValue({ success: true, data: [] }),
}
}));

describe('GlobalSearchModal', () => {
it('renders search results as buttons for accessibility', () => {
render(
<MemoryRouter>
<GlobalSearchModal isOpen={true} onClose={() => {}} />
</MemoryRouter>
);

// Default results (pages) should be visible
// We use a specific page title we know exists in PAGES_SEARCH
const dashboardResult = screen.getByText('Dashboard');
expect(dashboardResult).toBeInTheDocument();

// Check if the container of the result is a button
// We look for the closest button to the text "Dashboard"
// Note: In the current implementation, this is a div, so this test should fail
const button = dashboardResult.closest('button');

expect(button).toBeInTheDocument();
});
});
Loading