diff --git a/apps/api/src/app/tailwind.css b/apps/api/src/app/tailwind.css index f6ffa7d..512ddd6 100644 --- a/apps/api/src/app/tailwind.css +++ b/apps/api/src/app/tailwind.css @@ -290,3 +290,96 @@ font-size: 0.92em; } } + +/* Accessibility features */ +html.a11y-dyslexia, +html.a11y-dyslexia * { + font-family: "Comic Sans MS", "Comic Sans", "OpenDyslexic", cursive, sans-serif; +} + +html.a11y-spacing-medium *:not([data-slot="official-website-bar"] *) { + letter-spacing: 0.08em; + word-spacing: 0.16em; +} + +html.a11y-spacing-heavy *:not([data-slot="official-website-bar"] *) { + letter-spacing: 0.16em; + word-spacing: 0.32em; +} + +html.a11y-line-height-relaxed *:not([data-slot="official-website-bar"] *) { + line-height: 1.85; +} + +html.a11y-line-height-loose *:not([data-slot="official-website-bar"] *) { + line-height: 2.2; +} + +html.a11y-align-left *:not([data-slot="official-website-bar"] *) { + text-align: left; +} + +html.a11y-align-center *:not([data-slot="official-website-bar"] *) { + text-align: center; +} + +html.a11y-bigger-cursor, +html.a11y-bigger-cursor * { + cursor: + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath d='M0 0l9 26 5-11 11-5z' fill='%23000' stroke='%23fff' stroke-width='2'/%3E%3C/svg%3E"), + auto; +} + +html.a11y-pause-animations, +html.a11y-pause-animations * { + animation: none; + animation-play-state: paused; + transition: none; +} + +html.a11y-hide-images img:not([data-a11y-preserve]), +html.a11y-hide-images [style*="background-image"]:not([data-a11y-preserve]) { + visibility: hidden; +} + +html.high-contrast, +html.a11y-high-contrast { + filter: contrast(1.4); +} + +html.high-contrast:not(.dark) { + --color-foreground: #000000; + --color-border: #1a1a1a; + --color-input: #1a1a1a; +} + +html.high-contrast.dark { + --color-background: #000000; + --color-foreground: #ffffff; + --color-border: #ffffff; + --color-input: #ffffff; +} + +html.a11y-saturation-low { + filter: saturate(0.5); +} + +html.a11y-saturation-high { + filter: saturate(2); +} + +html.a11y-saturation-none { + filter: grayscale(1); +} + +html.a11y-filter-protanopia { + filter: url(#protanopia-filter); +} + +html.a11y-filter-deuteranopia { + filter: url(#deuteranopia-filter); +} + +html.a11y-filter-tritanopia { + filter: url(#tritanopia-filter); +} diff --git a/apps/api/src/components/modules/common/accessibility-menu.tsx b/apps/api/src/components/modules/common/accessibility-menu.tsx new file mode 100644 index 0000000..cea3d8a --- /dev/null +++ b/apps/api/src/components/modules/common/accessibility-menu.tsx @@ -0,0 +1,471 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { useAccessibility } from "@/hooks"; +import type { Locale } from "@/lib/i18n"; +import { cn } from "@/lib/utils"; +import { ASSETS, chromeControlClassName } from "./official-website-bar/constants"; + +export function AccessibilityMenu({ locale }: { locale: Locale }) { + const { + fontSize, + highContrast, + dyslexiaFont, + textSpacing, + lineHeight, + biggerCursor, + pauseAnimations, + hideImages, + colorFilter, + screenReader, + resolvedTheme, + setTheme, + handleFontSizeChange, + handleHighContrastChange, + handleDyslexiaChange, + handleTextSpacingChange, + handleLineHeightChange, + handleBiggerCursorChange, + handlePauseAnimationsChange, + handleHideImagesChange, + handleColorFilterChange, + setScreenReader, + handleResetAccessibility, + } = useAccessibility(locale); + + return ( + <> + {/* SVG Color Blindness Filters definition */} + + + + + + + + + + + + + + + + + {/* biome-ignore lint/performance/noImgElement: static vector icon */} + + + {locale === "ne" ? "पहुँच योग्यता" : "Accessibility"} + + {/* biome-ignore lint/performance/noImgElement: static vector icon */} + + + + + + {locale === "ne" ? "पहुँच योग्यता सुविधाहरू" : "Accessibility Options"} + + + {locale === "ne" ? "सबै रिसेट" : "Reset All"} + + + + {/* Section 1: Text & Reading */} + + + {locale === "ne" ? "अक्षर र पढाइ (Text & Reading)" : "Text & Reading"} + + + {/* Text Size */} + + + {locale === "ne" ? "अक्षर आकार (Text Size)" : "Text Size"} + + + handleFontSizeChange("small")} + > + A- + + handleFontSizeChange("normal")} + > + A + + handleFontSizeChange("large")} + > + A+ + + handleFontSizeChange("xlarge")} + > + A++ + + + + + {/* Dyslexia Font & Text Spacing */} + + + + {locale === "ne" ? "डिस्लेक्सिया फन्ट" : "Dyslexia Font"} + + handleDyslexiaChange(!dyslexiaFont)} + > + {dyslexiaFont + ? locale === "ne" + ? "सक्रिय" + : "Active" + : locale === "ne" + ? "साधारण" + : "Default"} + + + + + + {locale === "ne" ? "अक्षर अन्तर (Spacing)" : "Text Spacing"} + + + handleTextSpacingChange("normal")} + > + 1x + + handleTextSpacingChange("medium")} + > + 1.5x + + handleTextSpacingChange("heavy")} + > + 2x + + + + + + {/* Line Height */} + + + {locale === "ne" ? "लाइनको उचाइ (Line Height)" : "Line Height"} + + + handleLineHeightChange("normal")} + > + {locale === "ne" ? "साधारण" : "Normal"} + + handleLineHeightChange("relaxed")} + > + 1.85x + + handleLineHeightChange("loose")} + > + 2.2x + + + + + + {/* Section 2: Vision & Contrast */} + + + {locale === "ne" ? "कन्ट्रास्ट र रङ (Vision & Contrast)" : "Vision & Contrast"} + + + {/* Theme Mode */} + + + {locale === "ne" ? "उज्यालो र अँध्यारो मोड (Theme Mode)" : "Theme Mode"} + + + setTheme("light")} + > + {locale === "ne" ? "उज्यालो" : "Light"} + + setTheme("dark")} + > + {locale === "ne" ? "अँध्यारो" : "Dark"} + + + + + {/* High Contrast Mode */} + + + {locale === "ne" ? "उच्च कन्ट्रास्ट (High Contrast)" : "High Contrast"} + + + handleHighContrastChange(false)} + > + {locale === "ne" ? "साधारण" : "Normal"} + + handleHighContrastChange(true)} + > + {locale === "ne" ? "उच्च कन्ट्रास्ट" : "High Contrast"} + + + + + {/* Color Blindness Filters */} + + + {locale === "ne" ? "रङ फिल्टर (Color Filters)" : "Color Filters"} + + + handleColorFilterChange("none")} + > + {locale === "ne" ? "कुनै छैन" : "None"} + + handleColorFilterChange("protanopia")} + > + Protanopia + + handleColorFilterChange("deuteranopia")} + > + Deuteranopia + + handleColorFilterChange("tritanopia")} + > + Tritanopia + + handleColorFilterChange("grayscale")} + > + {locale === "ne" ? "ग्रेस्केल (Grayscale)" : "Grayscale"} + + + + + {/* Hide Images */} + + + {locale === "ne" ? "तस्बिरहरू लुकाउनुहोस्" : "Hide Images"} + + handleHideImagesChange(!hideImages)} + > + {hideImages + ? locale === "ne" + ? "लुकाइएको" + : "Hidden" + : locale === "ne" + ? "देखाइएको" + : "Visible"} + + + + + {/* Section 3: Assistive Technology */} + + + {locale === "ne" ? "सहायक उपकरणहरू (Assistive Tools)" : "Assistive Tools"} + + + {/* Screen Reader */} + + + {locale === "ne" ? "स्क्रिन रिडर (Text to Speech)" : "Screen Reader"} + + setScreenReader(!screenReader)} + > + {screenReader + ? locale === "ne" + ? "सक्रिय 🔊" + : "Active 🔊" + : locale === "ne" + ? "निष्क्रिय" + : "Off"} + + + + {/* Pause Animations */} + + + {locale === "ne" ? "एनिमेसन रोक्नुहोस्" : "Pause Animations"} + + handlePauseAnimationsChange(!pauseAnimations)} + > + {pauseAnimations + ? locale === "ne" + ? "रोकिएको ⏸" + : "Paused ⏸" + : locale === "ne" + ? "सामान्य" + : "Normal"} + + + + {/* Bigger Cursor */} + + + {locale === "ne" ? "ठूलो कर्सर" : "Bigger Cursor"} + + handleBiggerCursorChange(!biggerCursor)} + > + {biggerCursor + ? locale === "ne" + ? "सक्रिय 🔍" + : "Active 🔍" + : locale === "ne" + ? "साधारण" + : "Normal"} + + + + + {/* Reset Defaults */} + + + {locale === "ne" ? "पुनः सेट गर्नुहोस्" : "Reset Defaults"} + + + + + > + ); +} diff --git a/apps/api/src/components/modules/common/index.ts b/apps/api/src/components/modules/common/index.ts index 378c33e..9ab4a27 100644 --- a/apps/api/src/components/modules/common/index.ts +++ b/apps/api/src/components/modules/common/index.ts @@ -1,5 +1,7 @@ +export { AccessibilityMenu } from "./accessibility-menu"; export { ArrowLink } from "./arrow-link"; export { LoadingSkeleton } from "./loading-skeleton"; +export { OfficialWebsiteBar } from "./official-website-bar"; export { PageHeader } from "./page-header"; export { ErrorPanel, LoadingPanel } from "./query-state"; export { SectionHeading } from "./section-heading"; diff --git a/apps/api/src/components/modules/common/official-website-bar.tsx b/apps/api/src/components/modules/common/official-website-bar.tsx index 46b917f..c39b3a0 100644 --- a/apps/api/src/components/modules/common/official-website-bar.tsx +++ b/apps/api/src/components/modules/common/official-website-bar.tsx @@ -1,9 +1,6 @@ "use client"; import { usePathname, useRouter } from "next/navigation"; -import { useTheme } from "next-themes"; -import { useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, @@ -13,73 +10,14 @@ import { } from "@/components/ui/dropdown-menu"; import { LOCALES, type Locale } from "@/lib/i18n"; import { cn } from "@/lib/utils"; +import { AccessibilityMenu } from "./accessibility-menu"; +import { ASSETS, chromeControlClassName, LANGUAGES } from "./official-website-bar/constants"; -const ASSETS = { - emblem: "/official-website-bar/emblem-of-nepal.png", - accessibility: "/official-website-bar/accessibility.svg", - divider: "/official-website-bar/divider.svg", - language: "/official-website-bar/language.svg", - caretDown: "/official-website-bar/caret-down.svg", -} as const; - -const LANGUAGES = [ - { value: "en", label: "English" }, - { value: "ne", label: "नेपाली" }, -] as const; - -type FontSizeOption = "small" | "normal" | "large" | "xlarge"; - -const FONT_SCALES: Record = { - small: "90%", - normal: "100%", - large: "115%", - xlarge: "130%", -}; - -const chromeControlClassName = - "inline-flex items-center gap-1 rounded-md border border-transparent text-xs font-medium text-primary-foreground outline-none transition-all hover:bg-primary-foreground/10 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"; - -function MastheadIcon({ - src, - alt = "", - className, -}: { - src: string; - alt?: string; - className?: string; -}) { - return ( - // biome-ignore lint/performance/noImgElement: official bar static vector icon - - ); -} - -function OfficialWebsiteBar({ locale }: { locale: Locale }) { - const [fontSize, setFontSize] = useState("normal"); - const { resolvedTheme, setTheme } = useTheme(); +export function OfficialWebsiteBar({ locale }: { locale: Locale }) { const router = useRouter(); const pathname = usePathname(); const rest = pathname.split("/").filter(Boolean).slice(1).join("/"); - useEffect(() => { - const savedScale = localStorage.getItem("nepal-gov-font-size") as FontSizeOption | null; - if (savedScale && FONT_SCALES[savedScale]) { - setFontSize(savedScale); - document.documentElement.style.fontSize = FONT_SCALES[savedScale]; - } - }, []); - - const handleFontSizeChange = (size: FontSizeOption) => { - setFontSize(size); - localStorage.setItem("nepal-gov-font-size", size); - document.documentElement.style.fontSize = FONT_SCALES[size]; - }; - - const handleResetAccessibility = () => { - handleFontSizeChange("normal"); - setTheme("light"); - }; - const handleLanguageChange = (nextLocale: string) => { if (LOCALES.includes(nextLocale as Locale)) { router.push(`/${nextLocale}${rest.length > 0 ? `/${rest}` : ""}`); @@ -94,7 +32,14 @@ function OfficialWebsiteBar({ locale }: { locale: Locale }) { {/* biome-ignore lint/performance/noImgElement: national emblem */} - + {locale === "ne" ? "नेपाल सरकारको आधिकारिक वेबसाइट" @@ -103,111 +48,7 @@ function OfficialWebsiteBar({ locale }: { locale: Locale }) { - - - - - {locale === "ne" ? "पहुँच योग्यता" : "Accessibility"} - - - - - - - {locale === "ne" ? "पहुँच योग्यता सुविधाहरू" : "Accessibility (NEA)"} - - - - {/* Text Size Control */} - - - {locale === "ne" ? "अक्षर आकार (Text Size)" : "Text Size"} - - - handleFontSizeChange("small")} - > - A- - - handleFontSizeChange("normal")} - > - A - - handleFontSizeChange("large")} - > - A+ - - handleFontSizeChange("xlarge")} - > - A++ - - - - - {/* Contrast / Theme Control */} - - - {locale === "ne" ? "कन्ट्रास्ट र रङ (Contrast & Theme)" : "Contrast & Theme"} - - - setTheme("light")} - > - {locale === "ne" ? "साधारण" : "Light"} - - setTheme("dark")} - > - {locale === "ne" ? "उच्च कन्ट्रास्ट" : "High Contrast"} - - - - - {/* Reset Defaults */} - - - {locale === "ne" ? "पुनः सेट गर्नुहोस्" : "Reset Defaults"} - - - - + {/* biome-ignore lint/performance/noImgElement: divider */} @@ -220,9 +61,11 @@ function OfficialWebsiteBar({ locale }: { locale: Locale }) { )} aria-label="Language" > - + {/* biome-ignore lint/performance/noImgElement: static vector icon */} + {languageLabel} - + {/* biome-ignore lint/performance/noImgElement: static vector icon */} + @@ -240,5 +83,3 @@ function OfficialWebsiteBar({ locale }: { locale: Locale }) { ); } - -export { OfficialWebsiteBar }; diff --git a/apps/api/src/components/modules/common/official-website-bar/constants.ts b/apps/api/src/components/modules/common/official-website-bar/constants.ts new file mode 100644 index 0000000..9881d54 --- /dev/null +++ b/apps/api/src/components/modules/common/official-website-bar/constants.ts @@ -0,0 +1,36 @@ +import type { FontSizeOption } from "./types"; + +export const ASSETS = { + emblem: "/official-website-bar/emblem-of-nepal.png", + accessibility: "/official-website-bar/accessibility.svg", + divider: "/official-website-bar/divider.svg", + language: "/official-website-bar/language.svg", + caretDown: "/official-website-bar/caret-down.svg", +} as const; + +export const LANGUAGES = [ + { value: "en", label: "English" }, + { value: "ne", label: "नेपाली" }, +] as const; + +export const FONT_SCALES: Record = { + small: "90%", + normal: "100%", + large: "115%", + xlarge: "130%", +}; + +export const STORAGE_KEYS = { + fontSize: "nepal-gov-font-size", + highContrast: "nepal-gov-high-contrast", + dyslexia: "nepal-gov-dyslexia", + spacing: "nepal-gov-spacing", + lineHeight: "nepal-gov-line-height", + cursor: "nepal-gov-bigger-cursor", + animations: "nepal-gov-pause-animations", + hideImages: "nepal-gov-hide-images", + colorFilter: "nepal-gov-color-filter", +} as const; + +export const chromeControlClassName = + "inline-flex items-center gap-1 rounded-md border border-transparent text-xs font-medium text-primary-foreground outline-none transition-all hover:bg-primary-foreground/10 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"; diff --git a/apps/api/src/components/modules/common/official-website-bar/index.ts b/apps/api/src/components/modules/common/official-website-bar/index.ts new file mode 100644 index 0000000..e6869b1 --- /dev/null +++ b/apps/api/src/components/modules/common/official-website-bar/index.ts @@ -0,0 +1,2 @@ +export * from "./constants"; +export * from "./types"; diff --git a/apps/api/src/components/modules/common/official-website-bar/types.ts b/apps/api/src/components/modules/common/official-website-bar/types.ts new file mode 100644 index 0000000..87f218c --- /dev/null +++ b/apps/api/src/components/modules/common/official-website-bar/types.ts @@ -0,0 +1,31 @@ +export type FontSizeOption = "small" | "normal" | "large" | "xlarge"; +export type TextSpacingOption = "normal" | "medium" | "heavy"; +export type LineHeightOption = "normal" | "relaxed" | "loose"; +export type ColorFilterOption = "none" | "protanopia" | "deuteranopia" | "tritanopia" | "grayscale"; + +export interface AccessibilityState { + fontSize: FontSizeOption; + highContrast: boolean; + dyslexiaFont: boolean; + textSpacing: TextSpacingOption; + lineHeight: LineHeightOption; + biggerCursor: boolean; + pauseAnimations: boolean; + hideImages: boolean; + colorFilter: ColorFilterOption; + screenReader: boolean; +} + +export interface AccessibilityActions { + handleFontSizeChange: (size: FontSizeOption) => void; + handleHighContrastChange: (enabled: boolean) => void; + handleDyslexiaChange: (enabled: boolean) => void; + handleTextSpacingChange: (spacing: TextSpacingOption) => void; + handleLineHeightChange: (height: LineHeightOption) => void; + handleBiggerCursorChange: (enabled: boolean) => void; + handlePauseAnimationsChange: (enabled: boolean) => void; + handleHideImagesChange: (enabled: boolean) => void; + handleColorFilterChange: (filter: ColorFilterOption) => void; + setScreenReader: (enabled: boolean) => void; + handleResetAccessibility: () => void; +} diff --git a/apps/api/src/hooks/index.ts b/apps/api/src/hooks/index.ts index 3d3a4ae..a29a086 100644 --- a/apps/api/src/hooks/index.ts +++ b/apps/api/src/hooks/index.ts @@ -1,3 +1,4 @@ +export { type UseAccessibilityResult, useAccessibility } from "./use-accessibility"; export { type Actor, useActor } from "./use-actor"; export { ADMIN_TABS, type AdminTab, useAdminMembers } from "./use-admin-members"; export { type IssuesQuery, useIssueLabels, useProjectIssue, useProjectIssues } from "./use-issues"; diff --git a/apps/api/src/hooks/use-accessibility.ts b/apps/api/src/hooks/use-accessibility.ts new file mode 100644 index 0000000..c0644dd --- /dev/null +++ b/apps/api/src/hooks/use-accessibility.ts @@ -0,0 +1,241 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; +import { + FONT_SCALES, + STORAGE_KEYS, +} from "@/components/modules/common/official-website-bar/constants"; +import type { + AccessibilityActions, + AccessibilityState, + ColorFilterOption, + FontSizeOption, + LineHeightOption, + TextSpacingOption, +} from "@/components/modules/common/official-website-bar/types"; +import type { Locale } from "@/lib/i18n"; + +export interface UseAccessibilityResult extends AccessibilityState, AccessibilityActions { + resolvedTheme: string | undefined; + setTheme: (theme: string) => void; +} + +export function useAccessibility(locale: Locale): UseAccessibilityResult { + const [fontSize, setFontSize] = useState("normal"); + const [highContrast, setHighContrast] = useState(false); + const [dyslexiaFont, setDyslexiaFont] = useState(false); + const [textSpacing, setTextSpacing] = useState("normal"); + const [lineHeight, setLineHeight] = useState("normal"); + const [biggerCursor, setBiggerCursor] = useState(false); + const [pauseAnimations, setPauseAnimations] = useState(false); + const [hideImages, setHideImages] = useState(false); + const [colorFilter, setColorFilter] = useState("none"); + const [screenReader, setScreenReader] = useState(false); + + const { resolvedTheme, setTheme } = useTheme(); + + // Load and apply saved settings on mount + useEffect(() => { + const savedScale = localStorage.getItem(STORAGE_KEYS.fontSize) as FontSizeOption | null; + if (savedScale && FONT_SCALES[savedScale]) { + setFontSize(savedScale); + document.documentElement.style.fontSize = FONT_SCALES[savedScale]; + } + + const savedHighContrast = localStorage.getItem(STORAGE_KEYS.highContrast) === "true"; + if (savedHighContrast) { + setHighContrast(true); + document.documentElement.classList.add("high-contrast"); + } + + const savedDyslexia = localStorage.getItem(STORAGE_KEYS.dyslexia) === "true"; + if (savedDyslexia) { + setDyslexiaFont(true); + document.documentElement.classList.add("a11y-dyslexia"); + } + + const savedSpacing = localStorage.getItem(STORAGE_KEYS.spacing) as TextSpacingOption | null; + if (savedSpacing && savedSpacing !== "normal") { + setTextSpacing(savedSpacing); + document.documentElement.classList.add(`a11y-spacing-${savedSpacing}`); + } + + const savedLineHeight = localStorage.getItem( + STORAGE_KEYS.lineHeight, + ) as LineHeightOption | null; + if (savedLineHeight && savedLineHeight !== "normal") { + setLineHeight(savedLineHeight); + document.documentElement.classList.add(`a11y-line-height-${savedLineHeight}`); + } + + const savedCursor = localStorage.getItem(STORAGE_KEYS.cursor) === "true"; + if (savedCursor) { + setBiggerCursor(true); + document.documentElement.classList.add("a11y-bigger-cursor"); + } + + const savedAnimations = localStorage.getItem(STORAGE_KEYS.animations) === "true"; + if (savedAnimations) { + setPauseAnimations(true); + document.documentElement.classList.add("a11y-pause-animations"); + } + + const savedHideImages = localStorage.getItem(STORAGE_KEYS.hideImages) === "true"; + if (savedHideImages) { + setHideImages(true); + document.documentElement.classList.add("a11y-hide-images"); + } + + const savedColorFilter = localStorage.getItem( + STORAGE_KEYS.colorFilter, + ) as ColorFilterOption | null; + if (savedColorFilter && savedColorFilter !== "none") { + setColorFilter(savedColorFilter); + document.documentElement.classList.add(`a11y-filter-${savedColorFilter}`); + } + }, []); + + // Screen reader speech listener + useEffect(() => { + if (!screenReader) return; + if (typeof window === "undefined" || !("speechSynthesis" in window)) return; + + const speak = (text: string) => { + window.speechSynthesis.cancel(); + const utterance = new SpeechSynthesisUtterance(text); + utterance.lang = locale === "ne" ? "ne-NP" : "en-US"; + window.speechSynthesis.speak(utterance); + }; + + speak(locale === "ne" ? "स्क्रिन रिडर सक्रिय भयो" : "Screen reader activated"); + + const onFocusIn = (e: FocusEvent) => { + const el = e.target as HTMLElement | null; + if (!el) return; + const text = + el.getAttribute("aria-label") || + el.innerText || + el.getAttribute("title") || + el.getAttribute("alt") || + ""; + if (text.trim()) { + speak(text.trim().slice(0, 150)); + } + }; + + document.addEventListener("focusin", onFocusIn); + return () => { + document.removeEventListener("focusin", onFocusIn); + window.speechSynthesis.cancel(); + }; + }, [screenReader, locale]); + + const handleFontSizeChange = (size: FontSizeOption) => { + setFontSize(size); + localStorage.setItem(STORAGE_KEYS.fontSize, size); + document.documentElement.style.fontSize = FONT_SCALES[size]; + }; + + const handleHighContrastChange = (enabled: boolean) => { + setHighContrast(enabled); + localStorage.setItem(STORAGE_KEYS.highContrast, String(enabled)); + document.documentElement.classList.toggle("high-contrast", enabled); + }; + + const handleDyslexiaChange = (enabled: boolean) => { + setDyslexiaFont(enabled); + localStorage.setItem(STORAGE_KEYS.dyslexia, String(enabled)); + document.documentElement.classList.toggle("a11y-dyslexia", enabled); + }; + + const handleTextSpacingChange = (spacing: TextSpacingOption) => { + document.documentElement.classList.remove("a11y-spacing-medium", "a11y-spacing-heavy"); + setTextSpacing(spacing); + localStorage.setItem(STORAGE_KEYS.spacing, spacing); + if (spacing !== "normal") { + document.documentElement.classList.add(`a11y-spacing-${spacing}`); + } + }; + + const handleLineHeightChange = (height: LineHeightOption) => { + document.documentElement.classList.remove("a11y-line-height-relaxed", "a11y-line-height-loose"); + setLineHeight(height); + localStorage.setItem(STORAGE_KEYS.lineHeight, height); + if (height !== "normal") { + document.documentElement.classList.add(`a11y-line-height-${height}`); + } + }; + + const handleBiggerCursorChange = (enabled: boolean) => { + setBiggerCursor(enabled); + localStorage.setItem(STORAGE_KEYS.cursor, String(enabled)); + document.documentElement.classList.toggle("a11y-bigger-cursor", enabled); + }; + + const handlePauseAnimationsChange = (enabled: boolean) => { + setPauseAnimations(enabled); + localStorage.setItem(STORAGE_KEYS.animations, String(enabled)); + document.documentElement.classList.toggle("a11y-pause-animations", enabled); + }; + + const handleHideImagesChange = (enabled: boolean) => { + setHideImages(enabled); + localStorage.setItem(STORAGE_KEYS.hideImages, String(enabled)); + document.documentElement.classList.toggle("a11y-hide-images", enabled); + }; + + const handleColorFilterChange = (filter: ColorFilterOption) => { + document.documentElement.classList.remove( + "a11y-filter-protanopia", + "a11y-filter-deuteranopia", + "a11y-filter-tritanopia", + "a11y-filter-grayscale", + ); + setColorFilter(filter); + localStorage.setItem(STORAGE_KEYS.colorFilter, filter); + if (filter !== "none") { + document.documentElement.classList.add(`a11y-filter-${filter}`); + } + }; + + const handleResetAccessibility = () => { + handleFontSizeChange("normal"); + setTheme("light"); + handleHighContrastChange(false); + handleDyslexiaChange(false); + handleTextSpacingChange("normal"); + handleLineHeightChange("normal"); + handleBiggerCursorChange(false); + handlePauseAnimationsChange(false); + handleHideImagesChange(false); + handleColorFilterChange("none"); + setScreenReader(false); + }; + + return { + fontSize, + highContrast, + dyslexiaFont, + textSpacing, + lineHeight, + biggerCursor, + pauseAnimations, + hideImages, + colorFilter, + screenReader, + resolvedTheme, + setTheme, + handleFontSizeChange, + handleHighContrastChange, + handleDyslexiaChange, + handleTextSpacingChange, + handleLineHeightChange, + handleBiggerCursorChange, + handlePauseAnimationsChange, + handleHideImagesChange, + handleColorFilterChange, + setScreenReader, + handleResetAccessibility, + }; +}
{locale === "ne" ? "नेपाल सरकारको आधिकारिक वेबसाइट" @@ -103,111 +48,7 @@ function OfficialWebsiteBar({ locale }: { locale: Locale }) {