Skip to content

Commit

Permalink
a11y improvements (#12)
Browse files Browse the repository at this point in the history
* Return focus to previous element

* Bump version
  • Loading branch information
hwhmeikle authored Oct 10, 2024
1 parent 7072724 commit 3b9d685
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 37 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@authsignal/react",
"version": "0.0.12",
"version": "0.0.13",
"description": "",
"keywords": [
"authsignal",
Expand Down
20 changes: 5 additions & 15 deletions src/components/challenge/challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import { EmailOtpChallenge } from "./screens/email-otp-challenge";
import { PasskeyChallenge } from "./screens/passkey-challenge";
import { SmsOtpChallenge } from "./screens/sms-otp-challenge";
import { VerificationMethods } from "./screens/verification-methods";
import {
AuthChallengeContext,
useChallengeContext,
} from "./use-challenge-context";
import { ChallengeContext, useChallengeContext } from "./use-challenge-context";
import {
ChallengeProps,
TVerificationMethod,
Expand Down Expand Up @@ -84,17 +81,9 @@ export function Challenge({
[onChallengeSuccess],
);

const handleClose = React.useCallback(() => {
setOpen(false);

if (onCancel) {
onCancel();
}
}, [onCancel]);

const handleOpenChange = (open: boolean) => {
if (!open && onCancel) {
handleClose();
onCancel();
}

setOpen(open);
Expand Down Expand Up @@ -125,14 +114,15 @@ export function Challenge({
const style = createTheme(appearance);

return (
<AuthChallengeContext.Provider
<ChallengeContext.Provider
value={{
verificationMethod,
verificationMethods,
setVerificationMethod,
handleChallengeSuccess,
user,
authsignal,
isDesktop,
}}
>
{isDesktop ? (
Expand Down Expand Up @@ -160,7 +150,7 @@ export function Challenge({
</Drawer.Root>
)}
<div className="authsignal" style={style} ref={setContainer} />
</AuthChallengeContext.Provider>
</ChallengeContext.Provider>
);
}

Expand Down
10 changes: 7 additions & 3 deletions src/components/challenge/screens/authenticator-app-challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "../../../ui/form";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "../../../ui/input-otp";
import { useChallengeContext } from "../use-challenge-context";
import { DialogTitle } from "../../../ui/dialog";

type FormData = {
code: string;
Expand All @@ -29,7 +30,8 @@ export function AuthenticatorAppChallenge() {
OtpInputState.IDLE,
);

const { handleChallengeSuccess, authsignal } = useChallengeContext();
const { handleChallengeSuccess, authsignal, isDesktop } =
useChallengeContext();

const submitButtonRef = React.useRef<HTMLButtonElement>(null);

Expand Down Expand Up @@ -75,12 +77,14 @@ export function AuthenticatorAppChallenge() {
}
});

const TitleComponent = isDesktop ? DialogTitle : Drawer.Title;

return (
<div className="as-flex as-flex-col as-space-y-6">
<div className="as-space-y-2">
<Drawer.Title className="as-text-center as-text-xl as-font-medium">
<TitleComponent className="as-text-center as-text-xl as-font-medium">
Confirm it&apos;s you
</Drawer.Title>
</TitleComponent>
<p className="as-text-center as-text-sm">
Enter the code from your authenticator app to proceed.
</p>
Expand Down
10 changes: 7 additions & 3 deletions src/components/challenge/screens/email-otp-challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "../../../ui/form";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "../../../ui/input-otp";
import { useChallengeContext } from "../use-challenge-context";
import { DialogTitle } from "../../../ui/dialog";

type FormData = {
code: string;
Expand All @@ -29,7 +30,8 @@ export function EmailOtpChallenge() {
OtpInputState.IDLE,
);

const { handleChallengeSuccess, user, authsignal } = useChallengeContext();
const { handleChallengeSuccess, user, authsignal, isDesktop } =
useChallengeContext();

const submitButtonRef = React.useRef<HTMLButtonElement>(null);

Expand Down Expand Up @@ -79,12 +81,14 @@ export function EmailOtpChallenge() {
}
});

const TitleComponent = isDesktop ? DialogTitle : Drawer.Title;

return (
<div className="as-flex as-flex-col as-space-y-6">
<div className="as-space-y-2">
<Drawer.Title className="as-text-center as-text-xl as-font-medium">
<TitleComponent className="as-text-center as-text-xl as-font-medium">
Confirm it&apos;s you
</Drawer.Title>
</TitleComponent>
<p className="as-text-center as-text-sm">
Enter the code sent to {user?.email ?? ""} to proceed.
</p>
Expand Down
14 changes: 9 additions & 5 deletions src/components/challenge/screens/passkey-challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ReloadIcon } from "@radix-ui/react-icons";
import React, { useCallback } from "react";
import { Drawer } from "vaul";
import { useChallengeContext } from "../use-challenge-context";
import { DialogTitle } from "../../../ui/dialog";

type PasskeyChallengeProps = {
token: string; // TODO: This should be set in the web sdk
Expand All @@ -15,7 +16,8 @@ enum State {
export function PasskeyChallenge({ token }: PasskeyChallengeProps) {
const [state, setState] = React.useState<State>(State.AUTHENTICATING);

const { handleChallengeSuccess, authsignal } = useChallengeContext();
const { handleChallengeSuccess, authsignal, isDesktop } =
useChallengeContext();

const handlePasskeyAuthentication = useCallback(async () => {
const handleError = () => {
Expand Down Expand Up @@ -48,14 +50,16 @@ export function PasskeyChallenge({ token }: PasskeyChallengeProps) {
handlePasskeyAuthentication();
}, [handlePasskeyAuthentication]);

const TitleComponent = isDesktop ? DialogTitle : Drawer.Title;

return (
<div className="as-space-y-6">
{state === State.AUTHENTICATING && (
<>
<div className="as-space-y-2">
<Drawer.Title className="as-text-xl as-font-medium">
<TitleComponent className="as-text-xl as-font-medium">
Confirm it&apos;s you
</Drawer.Title>
</TitleComponent>
<p className="as-text-sm">
You will receive a prompt from your browser to authenticate with
your passkey.
Expand All @@ -68,9 +72,9 @@ export function PasskeyChallenge({ token }: PasskeyChallengeProps) {
{state === State.ERROR && (
<>
<div className="as-space-y-2">
<Drawer.Title className="as-text-xl as-font-medium">
<TitleComponent className="as-text-xl as-font-medium">
Confirm it&apos;s you
</Drawer.Title>
</TitleComponent>
<p className="as-text-sm">
There was a problem authenticating with your passkey.
</p>
Expand Down
10 changes: 7 additions & 3 deletions src/components/challenge/screens/sms-otp-challenge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "../../../ui/form";
import { InputOTP, InputOTPGroup, InputOTPSlot } from "../../../ui/input-otp";
import { useChallengeContext } from "../use-challenge-context";
import { DialogTitle } from "../../../ui/dialog";

type FormData = {
code: string;
Expand All @@ -29,7 +30,8 @@ export function SmsOtpChallenge() {
OtpInputState.IDLE,
);

const { handleChallengeSuccess, user, authsignal } = useChallengeContext();
const { handleChallengeSuccess, user, authsignal, isDesktop } =
useChallengeContext();

const submitButtonRef = React.useRef<HTMLButtonElement>(null);

Expand Down Expand Up @@ -79,12 +81,14 @@ export function SmsOtpChallenge() {
}
});

const TitleComponent = isDesktop ? DialogTitle : Drawer.Title;

return (
<div className="as-flex as-flex-col as-space-y-6">
<div className="as-space-y-2">
<Drawer.Title className="as-text-center as-text-xl as-font-medium">
<TitleComponent className="as-text-center as-text-xl as-font-medium">
Confirm it&apos;s you
</Drawer.Title>
</TitleComponent>
<p className="as-text-center as-text-sm">
Enter the code sent to {user?.phoneNumber ?? ""} to proceed.
</p>
Expand Down
9 changes: 7 additions & 2 deletions src/components/challenge/screens/verification-methods.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import { PasskeyIcon } from "../../icons/passkey-icon";
import { SmsOtpIcon } from "../../icons/sms-otp-icon";
import { useChallengeContext } from "../use-challenge-context";
import { TVerificationMethod, VerificationMethod } from "../../../types";
import { DialogTitle } from "../../../ui/dialog";

export function VerificationMethods() {
const { isDesktop } = useChallengeContext();

const TitleComponent = isDesktop ? DialogTitle : Drawer.Title;

return (
<div className="as-space-y-4">
<Drawer.Title className="as-text-center as-text-xl as-font-medium">
<TitleComponent className="as-text-center as-text-xl as-font-medium">
Select an authentication method
</Drawer.Title>
</TitleComponent>
<ul className="as-space-y-3">
<VerificationMethodItem
verificationMethod={VerificationMethod.PASSKEY}
Expand Down
11 changes: 6 additions & 5 deletions src/components/challenge/use-challenge-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Authsignal } from "@authsignal/browser";
import React from "react";
import { ChallengeProps, TVerificationMethod } from "../../types";

type AuthChallengeState = {
type ChallengeState = {
isDesktop: boolean;
verificationMethod?: TVerificationMethod;
setVerificationMethod: React.Dispatch<
React.SetStateAction<TVerificationMethod | undefined>
Expand All @@ -11,12 +12,12 @@ type AuthChallengeState = {
authsignal: Authsignal;
} & Pick<ChallengeProps, "user" | "verificationMethods">;

export const AuthChallengeContext = React.createContext<
AuthChallengeState | undefined
>(undefined);
export const ChallengeContext = React.createContext<ChallengeState | undefined>(
undefined,
);

export function useChallengeContext() {
const context = React.useContext(AuthChallengeContext);
const context = React.useContext(ChallengeContext);

if (!context) {
throw new Error("useChallengeContext must be used within a AuthChallenge");
Expand Down
22 changes: 22 additions & 0 deletions src/use-authsignal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ export function useAuthsignal() {
);
}

const previouslyFocusedElement =
document.activeElement as HTMLElement | null;

const returnFocus = () => {
previouslyFocusedElement?.focus();
};

const newChallenge: ChallengeProps = {
...options,

Expand All @@ -84,6 +91,8 @@ export function useAuthsignal() {

memoryChallengeState = undefined;
challengeEmitter.emit(memoryChallengeState);

returnFocus();
}, ANIMATION_DURATION);
},

Expand All @@ -95,6 +104,8 @@ export function useAuthsignal() {

memoryChallengeState = undefined;
challengeEmitter.emit(memoryChallengeState);

returnFocus();
}, ANIMATION_DURATION);
},
};
Expand All @@ -114,6 +125,13 @@ export function useAuthsignal() {
);
}

const previouslyFocusedElement =
document.activeElement as HTMLElement | null;

const returnFocus = () => {
previouslyFocusedElement?.focus();
};

return new Promise<{ token: string }>((resolve, reject) => {
const newChallenge: ChallengeProps = {
...options,
Expand All @@ -138,6 +156,8 @@ export function useAuthsignal() {

memoryChallengeState = undefined;
challengeEmitter.emit(memoryChallengeState);

returnFocus();
}, ANIMATION_DURATION);
},

Expand All @@ -149,6 +169,8 @@ export function useAuthsignal() {

memoryChallengeState = undefined;
challengeEmitter.emit(memoryChallengeState);

returnFocus();
}, ANIMATION_DURATION);
},
};
Expand Down

0 comments on commit 3b9d685

Please sign in to comment.