Skip to content

Commit

Permalink
fix: ♿️ fixed accessibility issues
Browse files Browse the repository at this point in the history
  • Loading branch information
dobromiryor committed Jul 5, 2024
1 parent 5020ec9 commit 9ca2919
Show file tree
Hide file tree
Showing 20 changed files with 251 additions and 94 deletions.
32 changes: 23 additions & 9 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,31 @@ module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
'react-refresh/only-export-components': [
'warn',
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
},
}
};
11 changes: 9 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import clsx from "clsx";
import { Alerts } from "./components/organisms/Alerts";
import { CurrentConditions } from "./components/organisms/CurrentConditions";
import { DailyForecast } from "./components/organisms/DailyForecast";
import { HourlyDetails } from "./components/organisms/HourlyDetails";
import { HourlyForecast } from "./components/organisms/HourlyForecast";
import { Navbar } from "./components/organisms/Navbar";
import { Now } from "./components/organisms/Now";
import { useModal } from "./hooks/useModal";

function App() {
const [isOpen] = useModal();

return (
<div className="mx-auto p-4 flex flex-col max-w-screen-sm gap-4">
<main
className={clsx("mx-auto p-4 flex flex-col max-w-screen-sm gap-4")}
tabIndex={isOpen ? -1 : 0}
>
<Navbar />
<Now />
<Alerts />
<HourlyForecast />
<DailyForecast />
<CurrentConditions />
<HourlyDetails />
</div>
</main>
);
}

Expand Down
9 changes: 6 additions & 3 deletions src/components/atoms/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ export const Card = ({
visualizationSRDescription,
}: CardProps) => {
return (
<div className="flex flex-col gap-1 p-4 bg-primary text-inverted-text dark:text-text rounded-xl text-sm">
<span>{title}</span>
<li
className="flex flex-col gap-1 p-4 bg-primary text-inverted-text dark:text-text rounded-xl text-sm"
tabIndex={0}
>
<h2>{title}</h2>

<div
className={clsx(
Expand Down Expand Up @@ -65,6 +68,6 @@ export const Card = ({
<span className="sr-only">{visualizationSRDescription}</span>
)}
</div>
</div>
</li>
);
};
51 changes: 42 additions & 9 deletions src/components/atoms/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import clsx from "clsx";
import {
Dispatch,
MouseEvent,
ReactNode,
SetStateAction,
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import { createPortal } from "react-dom";
import { useModal } from "../../hooks/useModal";
import { Icon } from "./Icon";

interface ModalProps {
title: string;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
children: ReactNode;
wrapperId?: string;
}
Expand All @@ -27,15 +27,17 @@ const createWrapperAndAppendToBody = (wrapperId: string) => {

export const Modal = ({
title,
isOpen,
setIsOpen,
children,
wrapperId = "portal-root",
}: ModalProps) => {
const [wrapperElement, setWrapperElement] = useState<HTMLElement | null>(
null
);

const modalRef = useRef<HTMLDivElement>(null);

const [isOpen, setIsOpen] = useModal();

useLayoutEffect(() => {
let element = document.getElementById(wrapperId);
let systemCreated = false;
Expand All @@ -54,12 +56,32 @@ export const Modal = ({
};
}, [wrapperId]);

const handleDismiss = () => setIsOpen(false);
const handleDismiss = useCallback(() => setIsOpen(false), [setIsOpen]);
const handleStopPropagation = (e: MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
return false;
};

useEffect(() => {
if (isOpen) {
modalRef.current?.focus();
}
}, [isOpen]);

useEffect(() => {
if (isOpen) {
const handleKeypress = (e: KeyboardEvent) => {
if (e.key === "Escape") {
handleDismiss();
}
};

document.addEventListener("keyup", handleKeypress);

return () => document.removeEventListener("keyup", handleKeypress);
}
}, [handleDismiss, isOpen]);

if (wrapperElement === null) return null;

return createPortal(
Expand All @@ -74,15 +96,26 @@ export const Modal = ({
onClick={handleDismiss}
>
<div
ref={modalRef}
role="dialog"
aria-modal={isOpen}
aria-labelledby={`modal-${title}`}
className={clsx(
"flex flex-col gap-4 w-full max-w-screen-sm max-h-screen p-4 m-4 rounded-xl bg-primary text-inverted-text dark:text-text origin-center transition-all duration-150 initial-scale-0 shadow-2xl",
isOpen ? "scale-100" : "scale-0"
)}
onClick={handleStopPropagation}
tabIndex={isOpen ? 0 : -1}
>
<div className="flex justify-between items-center">
<h1 className="font-bold text-xl">{title}</h1>
<button aria-label="Close modal" onClick={handleDismiss}>
<h1 id={`modal-${title}`} className="font-bold text-xl">
{title}
</h1>
<button
className="flex justify-center items-center w-6 h-6 "
aria-label="Close modal"
onClick={handleDismiss}
>
<Icon icon="close" size="24" aria-hidden />
</button>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/atoms/SegmentedButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ export const SegmentedButton = ({
selectedId,
}: SegmentedButtonProps) => {
return (
<div className="flex flex-col gap-2">
<section aria-labelledby={`${title}-label`} className="flex flex-col gap-2">
<span id={`${title}-label`} className="font-medium">
{title}
</span>
<div className="flex">
{buttons.map((item, index) => (
<button
aria-labelledby={`${title}-label`}
key={`Segmented__Button__${item.id}__${index}__${item.title}`}
onClick={item.onClick}
className={clsx(
Expand All @@ -34,11 +33,12 @@ export const SegmentedButton = ({
? "bg-accent hover:bg-accent/80"
: "hover:bg-accent/50"
)}
autoFocus={selectedId === item.id}
>
{item.title}
</button>
))}
</div>
</div>
</section>
);
};
15 changes: 15 additions & 0 deletions src/components/atoms/SkipToContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface SkipToContentProps {
destination: string;
href: string;
}

export const SkipToContent = ({ href, destination }: SkipToContentProps) => {
return (
<a
href={href}
className="fixed bottom-2 right-2 p-2 rounded-lg bg-primary transition-opacity opacity-0 focus:opacity-100 pointer-events-none focus:pointer-events-auto"
>
Skip to {destination}
</a>
);
};
4 changes: 3 additions & 1 deletion src/components/molecules/HumidityChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,18 @@ export const HumidityChart = () => {

return (
<ul
aria-label="Humidity details list"
className="flex gap-4 px-4 max-w-full overflow-x-scroll scrollbar-none"
tabIndex={0}
>
{data.hourly.map((item) => (
<li
className="flex flex-col justify-end items-center gap-1 h-28"
key={`Hourly__Rain__${item.dt}`}
tabIndex={0}
>
<span
aria-label="Humidity"
aria-description="Humidity"
className="text-xs text-inverted-text/75 dark:text-text/75"
>
{item.humidity}%
Expand Down
2 changes: 2 additions & 0 deletions src/components/molecules/RainChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ export const RainChart = () => {

return (
<ul
aria-label="Rain details list"
className="flex gap-4 px-4 max-w-full overflow-x-scroll scrollbar-none"
tabIndex={0}
>
{data.hourly.map((item) => (
<li
className="flex flex-col justify-end items-center gap-1 h-28"
key={`Hourly__Rain__${item.dt}`}
tabIndex={0}
>
{item.rain?.["1h"] ? (
<span
Expand Down
10 changes: 2 additions & 8 deletions src/components/molecules/SettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Dispatch, SetStateAction } from "react";
import { Theme } from "../../enums/theme.enum";
import { Unit } from "../../enums/unit.enum";
import { useTheme } from "../../hooks/useTheme";
Expand All @@ -7,19 +6,14 @@ import { useWeatherStore } from "../../stores/weather.store";
import { Modal } from "../atoms/Modal";
import { SegmentedButton } from "../atoms/SegmentedButton";

interface SettingsModalProps {
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
}

export const SettingsModal = ({ isOpen, setIsOpen }: SettingsModalProps) => {
export const SettingsModal = () => {
const { setDark, setDefault, setLight } = useTheme();

const { theme } = useThemeStore();
const { unit, setUnit } = useWeatherStore();

return (
<Modal title="Settings" isOpen={isOpen} setIsOpen={setIsOpen}>
<Modal title="Settings">
<div className="flex flex-col gap-2">
<SegmentedButton
title="Units"
Expand Down
7 changes: 5 additions & 2 deletions src/components/molecules/SunriseAndSunsetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ export const SunriseAndSunsetCard = () => {
}

return (
<div className="col-span-2 flex flex-col gap-1 p-4 bg-primary text-inverted-text dark:text-text rounded-xl text-sm">
<li
className="col-span-2 flex flex-col gap-1 p-4 bg-primary text-inverted-text dark:text-text rounded-xl text-sm"
tabIndex={0}
>
<span>Sunrise & sunset</span>

<div
Expand Down Expand Up @@ -64,6 +67,6 @@ export const SunriseAndSunsetCard = () => {
</div>
</div>
</div>
</div>
</li>
);
};
2 changes: 2 additions & 0 deletions src/components/molecules/WindChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ export const WindChart = () => {

return (
<ul
aria-label="Wind details list"
className="flex gap-4 px-4 max-w-full overflow-x-scroll scrollbar-none"
tabIndex={0}
>
{data.hourly.map((item) => (
<li
className="relative flex flex-col justify-end items-center gap-1 h-28"
key={`Hourly__Wind__${item.dt}`}
tabIndex={0}
>
<WindIcon
degree={item.wind_deg}
Expand Down
17 changes: 12 additions & 5 deletions src/components/organisms/CurrentConditions.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SkipToContent } from "../atoms/SkipToContent";
import { HumidityCard } from "../molecules/HumidityCard";
import { PressureCard } from "../molecules/PressureCard";
import { SunriseAndSunsetCard } from "../molecules/SunriseAndSunsetCard";
Expand All @@ -7,15 +8,21 @@ import { WindCard } from "../molecules/WindCard";

export const CurrentConditions = () => {
return (
<div className="flex flex-col gap-2 select-none">
<span>Current Conditions</span>
<div className="grid grid-cols-2 gap-2">
<article
id="current-conditions"
aria-labelledby="current-conditions-title"
className="flex flex-col gap-2 select-none"
>
<h1 id="current-conditions-title">Current Conditions</h1>
<SkipToContent href="#hourly-details" destination="hourly details" />

<ul className="grid grid-cols-2 gap-2">
<WindCard />
<HumidityCard />
<UVCard />
<PressureCard />
<SunriseAndSunsetCard />
</div>
</div>
</ul>
</article>
);
};
Loading

0 comments on commit 9ca2919

Please sign in to comment.