fix: news feature security, accessibility, performance improvements

- DOMPurify strict allowlist with sandbox on iframes
- safeColor() CSS injection prevention for category_color
- URL validation (http/https) for images and links
- encodeURIComponent for slug in API calls
- Keyboard navigation on news cards (Enter/Space)
- aria-label, aria-pressed, type=button on all buttons
- 44px touch targets on remaining buttons
- memo() wrappers + stable callbacks (fewer re-renders)
- Canvas draw call batching (~75% reduction)
- TipTap extensions useMemo deps fix ([t] -> [])
- Featured image loading=eager fetchPriority=high (LCP)
- Unsafe Number(id) NaN guard
This commit is contained in:
Fringg
2026-03-23 11:09:52 +03:00
parent 99fc33625e
commit 74e6d52fee
11 changed files with 574 additions and 162 deletions

View File

@@ -1,27 +1,45 @@
import { cn } from '../../lib/utils';
interface ToggleProps {
checked: boolean;
onChange: () => void;
disabled?: boolean;
'aria-label'?: string;
className?: string;
}
export function Toggle({ checked, onChange, disabled }: ToggleProps) {
export function Toggle({
checked,
onChange,
disabled,
'aria-label': ariaLabel,
className,
}: ToggleProps) {
return (
<button
type="button"
role="switch"
aria-checked={checked}
aria-label={ariaLabel}
onClick={onChange}
disabled={disabled}
className={`flex min-h-[44px] items-center ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`}
className={cn(
'flex min-h-[44px] items-center',
disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
className,
)}
>
<div
className={`relative h-8 w-14 rounded-full transition-colors ${
checked ? 'bg-accent-500' : 'bg-dark-600'
}`}
className={cn(
'relative h-8 w-14 rounded-full transition-colors',
checked ? 'bg-accent-500' : 'bg-dark-600',
)}
>
<div
className={`absolute left-1 top-1 h-6 w-6 rounded-full bg-white transition-transform duration-200 ${
checked ? 'translate-x-6' : 'translate-x-0'
}`}
className={cn(
'absolute left-1 top-1 h-6 w-6 rounded-full bg-white transition-transform duration-200',
checked ? 'translate-x-6' : 'translate-x-0',
)}
/>
</div>
</button>