mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-02 11:33:05 +02:00
A11y bug bash
This commit is contained in:
@@ -54,7 +54,13 @@ const CrackerPanel = lazy(() =>
|
||||
const SearchView = lazy(() =>
|
||||
import('./components/SearchView').then((m) => ({ default: m.SearchView }))
|
||||
);
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from './components/ui/sheet';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from './components/ui/sheet';
|
||||
import { Toaster, toast } from './components/ui/sonner';
|
||||
import { getStateKey } from './utils/conversationState';
|
||||
import { appendRawPacketUnique } from './utils/rawPacketIdentity';
|
||||
@@ -637,6 +643,12 @@ export function App() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-2 focus:bg-primary focus:text-primary-foreground"
|
||||
>
|
||||
Skip to content
|
||||
</a>
|
||||
{localLabel.text && (
|
||||
<div
|
||||
style={{
|
||||
@@ -665,12 +677,13 @@ export function App() {
|
||||
<SheetContent side="left" className="w-[280px] p-0 flex flex-col" hideCloseButton>
|
||||
<SheetHeader className="sr-only">
|
||||
<SheetTitle>Navigation</SheetTitle>
|
||||
<SheetDescription>Sidebar navigation</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="flex-1 overflow-hidden">{activeSidebarContent}</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
<main className="flex-1 flex flex-col bg-background min-w-0">
|
||||
<main id="main-content" className="flex-1 flex flex-col bg-background min-w-0">
|
||||
<div
|
||||
className={cn(
|
||||
'flex-1 flex flex-col min-h-0',
|
||||
@@ -680,9 +693,9 @@ export function App() {
|
||||
{activeConversation ? (
|
||||
activeConversation.type === 'map' ? (
|
||||
<>
|
||||
<div className="flex justify-between items-center px-4 py-2.5 border-b border-border font-semibold text-base">
|
||||
<h2 className="flex justify-between items-center px-4 py-2.5 border-b border-border font-semibold text-base">
|
||||
Node Map
|
||||
</div>
|
||||
</h2>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Suspense
|
||||
fallback={
|
||||
@@ -707,9 +720,9 @@ export function App() {
|
||||
</Suspense>
|
||||
) : activeConversation.type === 'raw' ? (
|
||||
<>
|
||||
<div className="flex justify-between items-center px-4 py-2.5 border-b border-border font-semibold text-base">
|
||||
<h2 className="flex justify-between items-center px-4 py-2.5 border-b border-border font-semibold text-base">
|
||||
Raw Packet Feed
|
||||
</div>
|
||||
</h2>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<RawPacketList packets={rawPackets} />
|
||||
</div>
|
||||
@@ -820,12 +833,12 @@ export function App() {
|
||||
|
||||
{showSettings && (
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
<div className="flex justify-between items-center px-4 py-2.5 border-b border-border font-semibold text-base">
|
||||
<h2 className="flex justify-between items-center px-4 py-2.5 border-b border-border font-semibold text-base">
|
||||
<span>Radio & Settings</span>
|
||||
<span className="text-sm text-muted-foreground hidden md:inline">
|
||||
{SETTINGS_SECTION_LABELS[settingsSection]}
|
||||
</span>
|
||||
</div>
|
||||
</h2>
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<Suspense
|
||||
fallback={
|
||||
@@ -865,6 +878,13 @@ export function App() {
|
||||
|
||||
{/* Global Cracker Panel - deferred until first opened, then kept mounted for state */}
|
||||
<div
|
||||
ref={(el) => {
|
||||
// Focus the panel when it becomes visible
|
||||
if (showCracker && el) {
|
||||
const focusable = el.querySelector<HTMLElement>('input, button:not([disabled])');
|
||||
if (focusable) setTimeout(() => focusable.focus(), 210);
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'border-t border-border bg-background transition-all duration-200 overflow-hidden',
|
||||
showCracker ? 'h-[275px]' : 'h-0'
|
||||
|
||||
@@ -25,6 +25,7 @@ export function BotCodeEditor({ value, onChange, id, height = '256px' }: BotCode
|
||||
}}
|
||||
className="text-sm"
|
||||
id={id}
|
||||
aria-label="Bot code editor"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { api } from '../api';
|
||||
import { formatTime } from '../utils/messageParser';
|
||||
import { isFavorite } from '../utils/favorites';
|
||||
import { handleKeyboardActivate } from '../utils/a11y';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from './ui/sheet';
|
||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './ui/sheet';
|
||||
import { toast } from './ui/sonner';
|
||||
import type { Channel, ChannelDetail, Favorite } from '../types';
|
||||
|
||||
@@ -65,6 +65,7 @@ export function ChannelInfoPane({
|
||||
<SheetContent side="right" className="w-full sm:max-w-[400px] p-0 flex flex-col">
|
||||
<SheetHeader className="sr-only">
|
||||
<SheetTitle>Channel Info</SheetTitle>
|
||||
<SheetDescription>Channel details and statistics</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
{loading && !detail ? (
|
||||
|
||||
@@ -117,6 +117,7 @@ export function ChatHeader({
|
||||
);
|
||||
}}
|
||||
title="Click to copy"
|
||||
aria-label={conversation.type === 'channel' ? 'Copy channel key' : 'Copy contact key'}
|
||||
>
|
||||
{conversation.type === 'channel' ? conversation.id.toLowerCase() : conversation.id}
|
||||
</span>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getMapFocusHash } from '../utils/urlHash';
|
||||
import { isFavorite } from '../utils/favorites';
|
||||
import { handleKeyboardActivate } from '../utils/a11y';
|
||||
import { ContactAvatar } from './ContactAvatar';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle } from './ui/sheet';
|
||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './ui/sheet';
|
||||
import { toast } from './ui/sonner';
|
||||
import type { Contact, ContactDetail, Favorite, RadioConfig } from '../types';
|
||||
|
||||
@@ -98,6 +98,7 @@ export function ContactInfoPane({
|
||||
<SheetContent side="right" className="w-full sm:max-w-[400px] p-0 flex flex-col">
|
||||
<SheetHeader className="sr-only">
|
||||
<SheetTitle>Contact Info</SheetTitle>
|
||||
<SheetDescription>Contact details and actions</SheetDescription>
|
||||
</SheetHeader>
|
||||
|
||||
{isNameOnly && nameOnlyValue ? (
|
||||
|
||||
@@ -586,7 +586,14 @@ export function CrackerPanel({
|
||||
: `${Math.round(progress.etaSeconds / 60)}m`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 bg-muted rounded overflow-hidden">
|
||||
<div
|
||||
className="h-2 bg-muted rounded overflow-hidden"
|
||||
role="progressbar"
|
||||
aria-valuenow={Math.round(progress.percent)}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-label="Cracking progress"
|
||||
>
|
||||
<div
|
||||
className="h-full bg-primary transition-all duration-200"
|
||||
style={{ width: `${progress.percent}%` }}
|
||||
@@ -597,12 +604,14 @@ export function CrackerPanel({
|
||||
|
||||
{/* GPU status */}
|
||||
{gpuAvailable === false && (
|
||||
<div className="text-sm text-destructive">
|
||||
<div className="text-sm text-destructive" role="alert">
|
||||
WebGPU not available. Cracking requires Chrome 113+ or Edge 113+.
|
||||
</div>
|
||||
)}
|
||||
{!wordlistLoaded && gpuAvailable !== false && (
|
||||
<div className="text-sm text-muted-foreground">Loading wordlist...</div>
|
||||
<div className="text-sm text-muted-foreground" role="status">
|
||||
Loading wordlist...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Cracked rooms list */}
|
||||
|
||||
@@ -140,22 +140,27 @@ export function MapView({ contacts, focusedKey }: MapViewProps) {
|
||||
</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-[#22c55e]" /> <1h
|
||||
<span className="w-3 h-3 rounded-full bg-[#22c55e]" aria-hidden="true" /> <1h
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-[#4ade80]" /> <1d
|
||||
<span className="w-3 h-3 rounded-full bg-[#4ade80]" aria-hidden="true" /> <1d
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-[#a3e635]" /> <3d
|
||||
<span className="w-3 h-3 rounded-full bg-[#a3e635]" aria-hidden="true" /> <3d
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-[#9ca3af]" /> older
|
||||
<span className="w-3 h-3 rounded-full bg-[#9ca3af]" aria-hidden="true" /> older
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Map - z-index constrained to stay below modals/sheets */}
|
||||
<div className="flex-1 relative" style={{ zIndex: 0 }}>
|
||||
<div
|
||||
className="flex-1 relative"
|
||||
style={{ zIndex: 0 }}
|
||||
role="img"
|
||||
aria-label="Map showing mesh node locations"
|
||||
>
|
||||
<MapContainer
|
||||
center={[20, 0]}
|
||||
zoom={2}
|
||||
|
||||
@@ -140,6 +140,7 @@ function HopCountBadge({ paths, onClick, variant }: HopCountBadgeProps) {
|
||||
onClick();
|
||||
}}
|
||||
title="View message path"
|
||||
aria-label={`${hopInfo.display}, view path`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
@@ -462,8 +463,6 @@ export function MessageList({
|
||||
className="h-full overflow-y-auto p-4 flex flex-col gap-0.5"
|
||||
ref={listRef}
|
||||
onScroll={handleScroll}
|
||||
aria-live="polite"
|
||||
aria-relevant="additions"
|
||||
>
|
||||
{loadingOlder && (
|
||||
<div className="text-center py-2 text-muted-foreground text-sm" role="status">
|
||||
@@ -629,6 +628,7 @@ export function MessageList({
|
||||
});
|
||||
}}
|
||||
title="View echo paths"
|
||||
aria-label={`Acknowledged, ${msg.acked} echo${msg.acked !== 1 ? 's' : ''} — view paths`}
|
||||
>{` ✓${msg.acked > 1 ? msg.acked : ''}`}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">{` ✓${msg.acked > 1 ? msg.acked : ''}`}</span>
|
||||
@@ -649,6 +649,7 @@ export function MessageList({
|
||||
});
|
||||
}}
|
||||
title="Message status"
|
||||
aria-label="No echoes yet — view message status"
|
||||
>
|
||||
{' '}
|
||||
?
|
||||
|
||||
@@ -29,7 +29,11 @@ export function NeighborsMiniMap({ neighbors, radioLat, radioLon, radioName }: P
|
||||
const center: [number, number] = hasRadio ? [radioLat!, radioLon!] : [valid[0].lat, valid[0].lon];
|
||||
|
||||
return (
|
||||
<div className="min-h-48 flex-1 rounded border border-border overflow-hidden">
|
||||
<div
|
||||
className="min-h-48 flex-1 rounded border border-border overflow-hidden"
|
||||
role="img"
|
||||
aria-label="Map showing repeater neighbor locations"
|
||||
>
|
||||
<MapContainer
|
||||
center={center}
|
||||
zoom={10}
|
||||
|
||||
@@ -1872,8 +1872,14 @@ export function PacketVisualizer3D({
|
||||
</label>
|
||||
{pruneStaleNodes && (
|
||||
<div className="flex items-center gap-2 pl-6">
|
||||
<span className="text-muted-foreground whitespace-nowrap">Window:</span>
|
||||
<label
|
||||
htmlFor="prune-window"
|
||||
className="text-muted-foreground whitespace-nowrap"
|
||||
>
|
||||
Window:
|
||||
</label>
|
||||
<input
|
||||
id="prune-window"
|
||||
type="number"
|
||||
min={1}
|
||||
max={60}
|
||||
@@ -1884,7 +1890,9 @@ export function PacketVisualizer3D({
|
||||
}}
|
||||
className="w-14 rounded border border-border bg-background px-2 py-0.5 text-sm"
|
||||
/>
|
||||
<span className="text-muted-foreground">min</span>
|
||||
<span className="text-muted-foreground" aria-hidden="true">
|
||||
min
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
@@ -1907,12 +1915,14 @@ export function PacketVisualizer3D({
|
||||
</label>
|
||||
<div className="flex flex-col gap-1 mt-1">
|
||||
<label
|
||||
htmlFor="viz-repulsion"
|
||||
className="text-muted-foreground"
|
||||
title="How strongly nodes repel each other. Higher values spread nodes out more."
|
||||
>
|
||||
Repulsion: {Math.abs(chargeStrength)}
|
||||
</label>
|
||||
<input
|
||||
id="viz-repulsion"
|
||||
type="range"
|
||||
min="50"
|
||||
max="2500"
|
||||
@@ -1923,12 +1933,14 @@ export function PacketVisualizer3D({
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 mt-1">
|
||||
<label
|
||||
htmlFor="viz-packet-speed"
|
||||
className="text-muted-foreground"
|
||||
title="How fast particles travel along links. Higher values make packets move faster."
|
||||
>
|
||||
Packet speed: {particleSpeedMultiplier}x
|
||||
</label>
|
||||
<input
|
||||
id="viz-packet-speed"
|
||||
type="range"
|
||||
min="1"
|
||||
max="5"
|
||||
|
||||
@@ -151,6 +151,7 @@ export function PathModal({
|
||||
)}
|
||||
<button
|
||||
onClick={toggleMap}
|
||||
aria-expanded={mapExpanded}
|
||||
className="text-xs text-primary hover:underline cursor-pointer shrink-0 ml-2"
|
||||
>
|
||||
{mapExpanded ? 'Hide map' : 'Map route'}
|
||||
|
||||
@@ -113,7 +113,12 @@ export function PathRouteMap({ resolved, senderInfo }: PathRouteMapProps) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded border border-border overflow-hidden" style={{ height: 220 }}>
|
||||
<div
|
||||
className="rounded border border-border overflow-hidden"
|
||||
role="img"
|
||||
aria-label="Map showing message route between nodes"
|
||||
style={{ height: 220 }}
|
||||
>
|
||||
<MapContainer
|
||||
center={center}
|
||||
zoom={10}
|
||||
|
||||
@@ -209,12 +209,7 @@ export function RawPacketList({ packets }: RawPacketListProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-full overflow-y-auto p-4 flex flex-col gap-2"
|
||||
ref={listRef}
|
||||
aria-live="polite"
|
||||
aria-relevant="additions"
|
||||
>
|
||||
<div className="h-full overflow-y-auto p-4 flex flex-col gap-2" ref={listRef}>
|
||||
{sortedPackets.map(({ packet, decoded }) => (
|
||||
<div
|
||||
key={getRawPacketObservationKey(packet)}
|
||||
@@ -231,9 +226,10 @@ export function RawPacketList({ packets }: RawPacketListProps) {
|
||||
|
||||
{/* Encryption status */}
|
||||
{!packet.decrypted && (
|
||||
<span title="Encrypted" aria-hidden="true">
|
||||
🔒
|
||||
</span>
|
||||
<>
|
||||
<span aria-hidden="true">🔒</span>
|
||||
<span className="sr-only">Encrypted</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Summary */}
|
||||
|
||||
@@ -172,9 +172,9 @@ export function SearchView({ contacts, channels, onNavigateToMessage }: SearchVi
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center px-4 py-2.5 border-b border-border font-semibold text-base">
|
||||
<h2 className="flex justify-between items-center px-4 py-2.5 border-b border-border font-semibold text-base">
|
||||
Message Search
|
||||
</div>
|
||||
</h2>
|
||||
|
||||
{/* Search input */}
|
||||
<div className="px-4 py-3 border-b border-border">
|
||||
@@ -254,7 +254,9 @@ export function SearchView({ contacts, channels, onNavigateToMessage }: SearchVi
|
||||
})}
|
||||
|
||||
{loading && (
|
||||
<div className="p-4 text-center text-muted-foreground text-sm">Searching...</div>
|
||||
<div className="p-4 text-center text-muted-foreground text-sm" role="status">
|
||||
Searching...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasMore && !loading && (
|
||||
|
||||
@@ -99,6 +99,7 @@ export function StatusBar({
|
||||
toast.success('Public key copied!');
|
||||
}}
|
||||
title="Click to copy public key"
|
||||
aria-label="Copy public key"
|
||||
>
|
||||
{config.public_key.toLowerCase()}
|
||||
</span>
|
||||
|
||||
@@ -40,8 +40,6 @@ export function ConsolePane({
|
||||
<div
|
||||
ref={outputRef}
|
||||
className="h-48 overflow-y-auto p-3 font-mono text-xs bg-console-bg/50 text-console space-y-1"
|
||||
aria-live="polite"
|
||||
aria-relevant="additions"
|
||||
>
|
||||
{history.length === 0 && (
|
||||
<p className="text-muted-foreground italic">Type a CLI command below...</p>
|
||||
|
||||
@@ -91,6 +91,7 @@ export function RadioSettingsPane({
|
||||
: 'text-success hover:bg-accent hover:text-success'
|
||||
)}
|
||||
title="Refresh Advert Intervals"
|
||||
aria-label="Refresh Advert Intervals"
|
||||
>
|
||||
<RefreshIcon
|
||||
className={cn(
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Label } from '../ui/label';
|
||||
import { Button } from '../ui/button';
|
||||
import { Separator } from '../ui/separator';
|
||||
import { toast } from '../ui/sonner';
|
||||
import { handleKeyboardActivate } from '../../utils/a11y';
|
||||
import type { AppSettings, AppSettingsUpdate, BotConfig, HealthStatus } from '../../types';
|
||||
|
||||
const BotCodeEditor = lazy(() =>
|
||||
@@ -191,14 +190,12 @@ export function SettingsBotSection({
|
||||
<div className="space-y-2">
|
||||
{bots.map((bot) => (
|
||||
<div key={bot.id} className="border border-input rounded-md overflow-hidden">
|
||||
<div
|
||||
className="flex items-center gap-2 px-3 py-2 bg-muted/50 cursor-pointer hover:bg-muted/80"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 px-3 py-2 bg-muted/50 cursor-pointer hover:bg-muted/80 w-full text-left"
|
||||
aria-expanded={expandedBotId === bot.id}
|
||||
onKeyDown={handleKeyboardActivate}
|
||||
onClick={(e) => {
|
||||
if ((e.target as HTMLElement).closest('input, button')) return;
|
||||
if ((e.target as HTMLElement).closest('input, [data-bot-control]')) return;
|
||||
setExpandedBotId(expandedBotId === bot.id ? null : bot.id);
|
||||
}}
|
||||
>
|
||||
@@ -220,15 +217,14 @@ export function SettingsBotSection({
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
aria-label="Bot name"
|
||||
className="px-2 py-0.5 text-sm bg-background border border-input rounded flex-1 max-w-[200px]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="text-sm font-medium flex-1 hover:text-primary cursor-text"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyboardActivate}
|
||||
className="text-sm font-medium flex-1 hover:text-primary cursor-text text-left"
|
||||
data-bot-control
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleStartEditingName(bot);
|
||||
@@ -241,6 +237,7 @@ export function SettingsBotSection({
|
||||
|
||||
<label
|
||||
className="flex items-center gap-1.5 cursor-pointer"
|
||||
data-bot-control
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<input
|
||||
@@ -248,6 +245,7 @@ export function SettingsBotSection({
|
||||
checked={bot.enabled}
|
||||
onChange={() => handleToggleBotEnabled(bot.id)}
|
||||
className="w-4 h-4 rounded border-input accent-primary"
|
||||
aria-label={`Enable ${bot.name}`}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">Enabled</span>
|
||||
</label>
|
||||
@@ -256,17 +254,18 @@ export function SettingsBotSection({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
data-bot-control
|
||||
className="h-6 w-6 p-0 text-muted-foreground hover:text-destructive"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteBot(bot.id);
|
||||
}}
|
||||
title="Delete bot"
|
||||
aria-label="Delete bot"
|
||||
aria-label={`Delete ${bot.name}`}
|
||||
>
|
||||
<span aria-hidden="true">🗑</span>
|
||||
</Button>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{expandedBotId === bot.id && (
|
||||
<div className="p-3 space-y-3 border-t border-input">
|
||||
|
||||
@@ -26,11 +26,12 @@ export function ThemeSelector() {
|
||||
|
||||
return (
|
||||
<fieldset className="flex flex-wrap gap-2">
|
||||
<legend className="sr-only">Color theme</legend>
|
||||
{THEMES.map((theme) => (
|
||||
<label
|
||||
key={theme.id}
|
||||
className={
|
||||
'flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer border transition-colors ' +
|
||||
'flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer border transition-colors focus-within:ring-2 focus-within:ring-ring ' +
|
||||
(current === theme.id
|
||||
? 'border-primary bg-primary/5'
|
||||
: 'border-transparent hover:bg-accent/50')
|
||||
|
||||
@@ -150,6 +150,7 @@ vi.mock('../components/ui/sheet', () => ({
|
||||
SheetContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetDescription: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/ui/sonner', () => ({
|
||||
|
||||
@@ -177,6 +177,7 @@ vi.mock('../components/ui/sheet', () => ({
|
||||
SheetContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetDescription: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/ui/sonner', () => ({
|
||||
|
||||
@@ -119,6 +119,7 @@ vi.mock('../components/ui/sheet', () => ({
|
||||
SheetContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetHeader: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
SheetDescription: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/ui/sonner', () => ({
|
||||
|
||||
Reference in New Issue
Block a user