fix: react-doctor small categories 68→72 (a11y+perf+style, 21 fixes)
- no-outline-none (2): add focus-visible outline to inline-styled inputs - no-redundant-roles (2): drop role="list" on <ul> - click-events-have-key-events (2): convert div backdrops to buttons - no-static-element-interactions (2): add role+tabIndex+onKeyDown to clickable divs - rendering-svg-precision (2): round SVG path decimals - no-tiny-text (2): 11px → 12px on CommandPalette hints - js-set-map-lookups (4→1): Sets for visibleItems, regex for keyword match - no-gray-on-colored-background (4): use color-matched text instead of stone-400/900 - no-transition-all (4): list specific properties in transition shorthand
This commit is contained in:
@@ -191,8 +191,12 @@ export default function AbandonedCartDashboard({ brandId }: Props) {
|
||||
|
||||
{/* Cart detail modal */}
|
||||
{cart && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm"
|
||||
onClick={() => setSelectedCart(null)}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Close abandoned cart details"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm border-0 cursor-default"
|
||||
onClick={() => setSelectedCart(null)}
|
||||
>
|
||||
<div className="bg-white border border-[var(--admin-border)] rounded-2xl w-full max-w-lg shadow-xl"
|
||||
onClick={e => e.stopPropagation()}>
|
||||
<div className="px-6 py-4 border-b border-[var(--admin-border)] flex items-center justify-between">
|
||||
@@ -246,7 +250,7 @@ export default function AbandonedCartDashboard({ brandId }: Props) {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -191,9 +191,10 @@ export default function AdminSidebar({
|
||||
* future iteration hides Harvest Reach for some role, the entire group
|
||||
* header goes away rather than rendering an empty section.
|
||||
*/
|
||||
const visibleItemSet = new Set(visibleItems);
|
||||
const visibleGroups: NavGroup[] = [];
|
||||
for (const group of NAV_GROUPS) {
|
||||
const items = group.items.filter((item) => visibleItems.includes(item));
|
||||
const items = group.items.filter((item) => visibleItemSet.has(item));
|
||||
if (items.length > 0) {
|
||||
visibleGroups.push({ ...group, items });
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ interface CardListProps {
|
||||
|
||||
export function CardList({ children, ariaLabel }: CardListProps) {
|
||||
return (
|
||||
<ul role="list" aria-label={ariaLabel} className="space-y-3 px-4 pb-24">
|
||||
<ul aria-label={ariaLabel} className="space-y-3 px-4 pb-24">
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
|
||||
@@ -322,13 +322,8 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
aria-label="Search command palette"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
className="flex-1 min-w-0 border-none outline-none focus-visible:outline-2 focus-visible:outline-[var(--color-accent)] focus-visible:outline-offset-2 bg-transparent text-base"
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
border: "none",
|
||||
outline: "none",
|
||||
background: "transparent",
|
||||
fontSize: "1rem",
|
||||
lineHeight: 1.4,
|
||||
color: "var(--admin-text-primary)",
|
||||
fontFamily: "inherit",
|
||||
@@ -424,7 +419,7 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "0.6875rem",
|
||||
fontSize: "0.75rem",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.05em",
|
||||
color: "var(--admin-text-muted)",
|
||||
@@ -445,7 +440,7 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
style={{
|
||||
padding: "0.5rem 1rem 0.625rem",
|
||||
borderTop: "1px solid var(--admin-border)",
|
||||
fontSize: "0.6875rem",
|
||||
fontSize: "0.75rem",
|
||||
color: "var(--admin-text-muted)",
|
||||
textAlign: "center",
|
||||
letterSpacing: "0.02em",
|
||||
|
||||
@@ -355,7 +355,7 @@ export default function ContactListPanel({
|
||||
<button type="button"
|
||||
onClick={() => handleDelete(c.id)}
|
||||
disabled={deleting === c.id}
|
||||
className="p-1.5 rounded-lg hover:bg-red-50 text-stone-400 hover:text-red-500 disabled:opacity-50 transition-colors"
|
||||
className="p-1.5 rounded-lg hover:bg-red-50 text-red-500 hover:text-red-700 disabled:opacity-50 transition-colors"
|
||||
title="Delete contact"
|
||||
>
|
||||
{Icons.trash("h-4 w-4")}
|
||||
@@ -386,7 +386,7 @@ export default function ContactListPanel({
|
||||
<button type="button"
|
||||
onClick={() => handleDelete(c.id)}
|
||||
disabled={deleting === c.id}
|
||||
className="p-1.5 rounded-lg hover:bg-red-50 text-stone-400 hover:text-red-500 disabled:opacity-50"
|
||||
className="p-1.5 rounded-lg hover:bg-red-50 text-red-500 hover:text-red-700 disabled:opacity-50"
|
||||
>
|
||||
{Icons.trash("h-4 w-4")}
|
||||
</button>
|
||||
|
||||
@@ -89,7 +89,15 @@ export default function SegmentListSidebar({ segments, activeSegmentId, onSelect
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => onSelect(segment)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
onSelect(segment);
|
||||
}
|
||||
}}
|
||||
className={`px-3 py-2.5 rounded-xl cursor-pointer flex items-center justify-between gap-2 transition-all ${
|
||||
activeSegmentId === segment.id
|
||||
? "bg-[var(--admin-accent-light)] border border-[var(--admin-accent)]"
|
||||
|
||||
@@ -297,7 +297,7 @@ export default function ScheduleImportModal({ brandId, onClose, onComplete }: Pr
|
||||
<td className="px-2 py-1.5">
|
||||
<button type="button"
|
||||
onClick={() => removeStop(idx)}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-lg text-stone-400 hover:bg-red-50 hover:text-red-600 transition-colors"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-lg text-red-500 hover:bg-red-50 hover:text-red-700 transition-colors"
|
||||
>
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function SideNavGroup({
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<ul className="space-y-1" role="list" hidden={collapsed}>
|
||||
<ul className="space-y-1" hidden={collapsed}>
|
||||
{children}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -55,10 +55,8 @@ export function StopsDatePicker({ value }: StopsDatePickerProps) {
|
||||
if (!newDate) return;
|
||||
router.push(`/admin/v2/stops?date=${newDate}`);
|
||||
}}
|
||||
className="bg-transparent border-0 outline-none focus-visible:outline-2 focus-visible:outline-[var(--color-accent)] focus-visible:outline-offset-2"
|
||||
style={{
|
||||
background: "transparent",
|
||||
border: 0,
|
||||
outline: "none",
|
||||
fontSize: "15px",
|
||||
fontWeight: 600,
|
||||
color: "var(--color-text)",
|
||||
|
||||
Reference in New Issue
Block a user