Skip to content

Commit

Permalink
Merge pull request #64 from aversini/feat-introducing-LiveRegion-comp…
Browse files Browse the repository at this point in the history
…onent

feat: introducing LiveRegion component
  • Loading branch information
aversini authored Nov 22, 2023
2 parents 5a47ee3 + 7976523 commit 32cb46b
Show file tree
Hide file tree
Showing 6 changed files with 689 additions and 2 deletions.
4 changes: 2 additions & 2 deletions packages/ui-components/src/common/hooks/useUniqueId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import { useRef } from "react";
* // -> inputId = "av-text-input-42"
*
* const inputHintId = useUniqueId({
* prefix: "pnr-text-input-hint-",
* prefix: "av-text-input-hint-",
* });
* // -> inputHintId = "pnr-text-input-hint-1j3h4f5"
* // -> inputHintId = "av-text-input-hint-1j3h4f5"
*
*/

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import clsx from "clsx";
import { useEffect, useRef } from "react";

import { VISUALLY_HIDDEN_CLASSNAME } from "../../../common/constants";
import { DEFAULT_POLITENESS_BY_ROLE } from "./constants";
import type { LiveRegionProps } from "./LiveRegionTypes";
import { conditionallyDelayAnnouncement } from "./utilities";

/**
* The `LiveRegion` component abstracts the logic for
* rendering live region content that consistently announces
* across assistive technologies (e.g. JAWS, VoiceOver,
* NVDA).
*/
export function LiveRegion({
children,
className,
politeness: politenessProp,
role = null,
announcementDelay,
clearAnnouncementDelay,
onAnnouncementClear,
visible,

...otherProps
}: LiveRegionProps) {
const liveRegionRef = useRef<HTMLDivElement>(null);
const announcementTimeoutRef = useRef();
const clearAnnouncementTimeoutRef = useRef();

let politeness = politenessProp;
/**
* We check `undefined` since it is our default,
* and we want to honor when the user supplies `null`.
*/
if (typeof politeness === "undefined") {
politeness = role ? DEFAULT_POLITENESS_BY_ROLE[role] : "assertive";
}

useEffect(() => {
conditionallyDelayAnnouncement({
announcementTimeoutRef,
announcementDelay,
children,
liveRegionRef,
clearAnnouncementDelay,
clearAnnouncementTimeoutRef,
onAnnouncementClear,
});
}, [
children,
announcementDelay,
clearAnnouncementDelay,
onAnnouncementClear,
]);

const generatedClassName = clsx(className, {
[VISUALLY_HIDDEN_CLASSNAME]: !visible,
});

return (
<div
ref={liveRegionRef}
aria-live={politeness as "polite" | "assertive" | "off" | undefined}
{...(role && { role: role })}
className={generatedClassName}
{...otherProps}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ROLES } from "./constants";

export type PolitenessByRole = {
[key: string]: any;
};

export type ClearAnnouncementProps = {
liveRegionRef: React.RefObject<HTMLElement | undefined>;
onAnnouncementClear?: () => void;
};

export type announceProps = {
children: React.ReactNode;
liveRegionRef: React.RefObject<HTMLElement | undefined>;
clearAnnouncementDelay?: number;
clearAnnouncementTimeoutRef: React.MutableRefObject<
NodeJS.Timeout | number | null | undefined
>;
onAnnouncementClear?: () => void;
};

export type conditionallyDelayAnnouncementProps = {
children: React.ReactNode;
liveRegionRef: React.RefObject<HTMLElement | undefined>;
announcementTimeoutRef: React.MutableRefObject<
NodeJS.Timeout | null | undefined
>;
announcementDelay?: number;
clearAnnouncementDelay?: number;
clearAnnouncementTimeoutRef: React.MutableRefObject<
NodeJS.Timeout | number | null | undefined
>;
onAnnouncementClear?: () => void;
};

export type LiveRegionProps = {
/**
* The content to be announced by the live region.
*/
children: React.ReactNode;
/**
* The `className` to apply to the live region.
*/
className?: string;
/**
* The `aria-live` politeness level to apply to the live region.
*/
politeness?: "polite" | "assertive" | "off" | null | undefined;
/**
* The `role` to apply to the live region.
*/
role?: typeof ROLES.ALERT | "status" | null | undefined;
/**
* Whether or not the live region should be visible.
*/
visible?: boolean;
/**
* The number of milliseconds to wait before announcing the content.
*/
announcementDelay?: number;
/**
* The number of milliseconds to wait before clearing the announcement.
*/
clearAnnouncementDelay?: number;
/**
* A callback to be invoked when the announcement is cleared.
*/
onAnnouncementClear?: () => void;
};
Loading

0 comments on commit 32cb46b

Please sign in to comment.