style: format all files with prettier

This commit is contained in:
Seth Hobson
2026-01-19 17:07:03 -05:00
parent 8d37048deb
commit 56848874a2
355 changed files with 15215 additions and 10241 deletions

View File

@@ -5,8 +5,8 @@
### Modal Dialog
```tsx
import { useEffect, useRef, type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { useEffect, useRef, type ReactNode } from "react";
import { createPortal } from "react-dom";
interface ModalProps {
isOpen: boolean;
@@ -23,27 +23,27 @@ export function Modal({ isOpen, onClose, title, children }: ModalProps) {
if (isOpen) {
previousActiveElement.current = document.activeElement;
dialogRef.current?.focus();
document.body.style.overflow = 'hidden';
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = '';
document.body.style.overflow = "";
(previousActiveElement.current as HTMLElement)?.focus();
}
return () => {
document.body.style.overflow = '';
document.body.style.overflow = "";
};
}, [isOpen]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
if (e.key === 'Tab') trapFocus(e, dialogRef.current);
if (e.key === "Escape") onClose();
if (e.key === "Tab") trapFocus(e, dialogRef.current);
};
if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
document.addEventListener("keydown", handleKeyDown);
}
return () => document.removeEventListener('keydown', handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [isOpen, onClose]);
if (!isOpen) return null;
@@ -82,7 +82,7 @@ export function Modal({ isOpen, onClose, title, children }: ModalProps) {
<div className="mt-4">{children}</div>
</div>
</div>,
document.body
document.body,
);
}
@@ -90,7 +90,7 @@ function trapFocus(e: KeyboardEvent, container: HTMLElement | null) {
if (!container) return;
const focusableElements = container.querySelectorAll<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
@@ -108,7 +108,7 @@ function trapFocus(e: KeyboardEvent, container: HTMLElement | null) {
### Dropdown Menu
```tsx
import { useState, useRef, useEffect, type ReactNode } from 'react';
import { useState, useRef, useEffect, type ReactNode } from "react";
interface DropdownProps {
trigger: ReactNode;
@@ -124,22 +124,25 @@ export function Dropdown({ trigger, children, label }: DropdownProps) {
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
if (
containerRef.current &&
!containerRef.current.contains(e.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const handleKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'Escape':
case "Escape":
setIsOpen(false);
triggerRef.current?.focus();
break;
case 'ArrowDown':
case "ArrowDown":
e.preventDefault();
if (!isOpen) {
setIsOpen(true);
@@ -147,17 +150,17 @@ export function Dropdown({ trigger, children, label }: DropdownProps) {
focusNextItem(menuRef.current, 1);
}
break;
case 'ArrowUp':
case "ArrowUp":
e.preventDefault();
if (isOpen) {
focusNextItem(menuRef.current, -1);
}
break;
case 'Home':
case "Home":
e.preventDefault();
focusFirstItem(menuRef.current);
break;
case 'End':
case "End":
e.preventDefault();
focusLastItem(menuRef.current);
break;
@@ -177,7 +180,7 @@ export function Dropdown({ trigger, children, label }: DropdownProps) {
{trigger}
<ChevronDownIcon
aria-hidden="true"
className={`transition-transform ${isOpen ? 'rotate-180' : ''}`}
className={`transition-transform ${isOpen ? "rotate-180" : ""}`}
/>
</button>
@@ -217,18 +220,26 @@ export function MenuItem({ children, onClick, disabled }: MenuItemProps) {
function focusNextItem(menu: HTMLElement | null, direction: 1 | -1) {
if (!menu) return;
const items = menu.querySelectorAll<HTMLElement>('[role="menuitem"]:not([disabled])');
const currentIndex = Array.from(items).indexOf(document.activeElement as HTMLElement);
const items = menu.querySelectorAll<HTMLElement>(
'[role="menuitem"]:not([disabled])',
);
const currentIndex = Array.from(items).indexOf(
document.activeElement as HTMLElement,
);
const nextIndex = (currentIndex + direction + items.length) % items.length;
items[nextIndex]?.focus();
}
function focusFirstItem(menu: HTMLElement | null) {
menu?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])')?.focus();
menu
?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])')
?.focus();
}
function focusLastItem(menu: HTMLElement | null) {
const items = menu?.querySelectorAll<HTMLElement>('[role="menuitem"]:not([disabled])');
const items = menu?.querySelectorAll<HTMLElement>(
'[role="menuitem"]:not([disabled])',
);
items?.[items.length - 1]?.focus();
}
```
@@ -236,7 +247,13 @@ function focusLastItem(menu: HTMLElement | null) {
### Combobox / Autocomplete
```tsx
import { useState, useRef, useId, type ChangeEvent, type KeyboardEvent } from 'react';
import {
useState,
useRef,
useId,
type ChangeEvent,
type KeyboardEvent,
} from "react";
interface Option {
value: string;
@@ -259,7 +276,7 @@ export function Combobox({
placeholder,
}: ComboboxProps) {
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState('');
const [inputValue, setInputValue] = useState("");
const [activeIndex, setActiveIndex] = useState(-1);
const inputRef = useRef<HTMLInputElement>(null);
@@ -268,7 +285,7 @@ export function Combobox({
const listboxId = useId();
const filteredOptions = options.filter((option) =>
option.label.toLowerCase().includes(inputValue.toLowerCase())
option.label.toLowerCase().includes(inputValue.toLowerCase()),
);
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
@@ -286,27 +303,27 @@ export function Combobox({
const handleKeyDown = (e: KeyboardEvent) => {
switch (e.key) {
case 'ArrowDown':
case "ArrowDown":
e.preventDefault();
if (!isOpen) {
setIsOpen(true);
} else {
setActiveIndex((prev) =>
prev < filteredOptions.length - 1 ? prev + 1 : prev
prev < filteredOptions.length - 1 ? prev + 1 : prev,
);
}
break;
case 'ArrowUp':
case "ArrowUp":
e.preventDefault();
setActiveIndex((prev) => (prev > 0 ? prev - 1 : prev));
break;
case 'Enter':
case "Enter":
e.preventDefault();
if (activeIndex >= 0 && filteredOptions[activeIndex]) {
handleSelect(filteredOptions[activeIndex]);
}
break;
case 'Escape':
case "Escape":
setIsOpen(false);
break;
}
@@ -353,8 +370,8 @@ export function Combobox({
aria-selected={activeIndex === index}
onClick={() => handleSelect(option)}
className={`cursor-pointer px-3 py-2 ${
activeIndex === index ? 'bg-blue-100' : 'hover:bg-gray-100'
} ${value === option.value ? 'font-medium' : ''}`}
activeIndex === index ? "bg-blue-100" : "hover:bg-gray-100"
} ${value === option.value ? "font-medium" : ""}`}
>
{option.label}
</li>
@@ -375,7 +392,7 @@ export function Combobox({
### Form Validation
```tsx
import { useId, type FormEvent } from 'react';
import { useId, type FormEvent } from "react";
interface FormFieldProps {
label: string;
@@ -383,12 +400,17 @@ interface FormFieldProps {
required?: boolean;
children: (props: {
id: string;
'aria-describedby': string | undefined;
'aria-invalid': boolean;
"aria-describedby": string | undefined;
"aria-invalid": boolean;
}) => ReactNode;
}
export function FormField({ label, error, required, children }: FormFieldProps) {
export function FormField({
label,
error,
required,
children,
}: FormFieldProps) {
const id = useId();
const errorId = `${id}-error`;
@@ -405,8 +427,8 @@ export function FormField({ label, error, required, children }: FormFieldProps)
{children({
id,
'aria-describedby': error ? errorId : undefined,
'aria-invalid': !!error,
"aria-describedby": error ? errorId : undefined,
"aria-invalid": !!error,
})}
{error && (
@@ -436,13 +458,16 @@ function ContactForm() {
type="email"
required
className={`w-full rounded border px-3 py-2 ${
props['aria-invalid'] ? 'border-red-500' : 'border-gray-300'
props["aria-invalid"] ? "border-red-500" : "border-gray-300"
}`}
/>
)}
</FormField>
<button type="submit" className="mt-4 px-4 py-2 bg-blue-600 text-white rounded">
<button
type="submit"
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded"
>
Submit
</button>
</form>
@@ -476,19 +501,22 @@ export function SkipLinks() {
## Live Regions
```tsx
import { useState, useEffect } from 'react';
import { useState, useEffect } from "react";
interface LiveAnnouncerProps {
message: string;
politeness?: 'polite' | 'assertive';
politeness?: "polite" | "assertive";
}
export function LiveAnnouncer({ message, politeness = 'polite' }: LiveAnnouncerProps) {
const [announcement, setAnnouncement] = useState('');
export function LiveAnnouncer({
message,
politeness = "polite",
}: LiveAnnouncerProps) {
const [announcement, setAnnouncement] = useState("");
useEffect(() => {
// Clear first, then set - ensures screen readers pick up the change
setAnnouncement('');
setAnnouncement("");
const timer = setTimeout(() => setAnnouncement(message), 100);
return () => clearTimeout(timer);
}, [message]);
@@ -506,9 +534,15 @@ export function LiveAnnouncer({ message, politeness = 'polite' }: LiveAnnouncerP
}
// Usage in a search component
function SearchResults({ results, loading }: { results: Item[]; loading: boolean }) {
function SearchResults({
results,
loading,
}: {
results: Item[];
loading: boolean;
}) {
const message = loading
? 'Loading results...'
? "Loading results..."
: `${results.length} results found`;
return (
@@ -544,12 +578,14 @@ function useFocusTrap(containerRef: RefObject<HTMLElement>, isActive: boolean) {
if (!isActive || !containerRef.current) return;
const container = containerRef.current;
const focusableSelector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const focusableSelector =
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Tab') return;
if (e.key !== "Tab") return;
const focusableElements = container.querySelectorAll<HTMLElement>(focusableSelector);
const focusableElements =
container.querySelectorAll<HTMLElement>(focusableSelector);
const first = focusableElements[0];
const last = focusableElements[focusableElements.length - 1];
@@ -562,8 +598,8 @@ function useFocusTrap(containerRef: RefObject<HTMLElement>, isActive: boolean) {
}
};
container.addEventListener('keydown', handleKeyDown);
return () => container.removeEventListener('keydown', handleKeyDown);
container.addEventListener("keydown", handleKeyDown);
return () => container.removeEventListener("keydown", handleKeyDown);
}, [containerRef, isActive]);
}
```
@@ -595,8 +631,12 @@ function getContrastRatio(fg: string, bg: string): number {
return (lighter + 0.05) / (darker + 0.05);
}
function meetsWCAG(fg: string, bg: string, level: 'AA' | 'AAA' = 'AA'): boolean {
function meetsWCAG(
fg: string,
bg: string,
level: "AA" | "AAA" = "AA",
): boolean {
const ratio = getContrastRatio(fg, bg);
return level === 'AAA' ? ratio >= 7 : ratio >= 4.5;
return level === "AAA" ? ratio >= 7 : ratio >= 4.5;
}
```