Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP/DO NOT MERGE]: update accessibility/keyboard navigation of ApiModal #7958

Draft
wants to merge 5 commits into
base: feature/api-docs-template
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/components/ApiDocs/ApiComment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ interface ApiCommentProps {

export const ApiComment = ({ apiComment, codeBlock }: ApiCommentProps) => {
if (!apiComment) return null;
const firstItem = apiComment[0];
if (!firstItem.text.replaceAll('-', '').trim()) {
const firstItem = apiComment[0]?.text
? apiComment[0].text.replaceAll('-', '').trim()
: null;
if (!firstItem) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I broke this, so re-fixing it.

apiComment.shift();
}
const commentList = apiComment.map((snippet, idx) => {
Expand Down
38 changes: 33 additions & 5 deletions src/components/ApiDocs/ApiModalProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { useState, createContext } from 'react';
import { useState, createContext, useRef, RefObject } from 'react';
import { LinkDataType } from './display/TypeLink';
import { ApiModal } from './display/ApiModal';

export const TypeContext = createContext({
interface TypeContextInterface {
setModalData: (data: any) => void;
setModalTriggerRef: (ref: RefObject<HTMLButtonElement> | null) => void;
modalTriggerRef: RefObject<HTMLButtonElement> | null;
openModal: () => void;
addBreadCrumb: (data: any) => void;
setBC: (data: any) => void;
}

export const TypeContext = createContext<TypeContextInterface>({
setModalData: (data) => data,
modalOpen: () => {},
setModalTriggerRef: (ref) => ref,
modalTriggerRef: null,
openModal: () => {},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed modalOpen -> openModal so it's a little more action-sounding, otherwise my first thought was that it is a boolean.

addBreadCrumb: (data) => data,
setBC: (data) => data
});
Expand All @@ -13,12 +24,26 @@ export const ApiModalProvider = ({ children }) => {
const [modalData, setModalData] = useState({});
const [showModal, setShowModal] = useState(false);
const [breadCrumbs, setBreadCrumbs] = useState<LinkDataType[]>([]);
const [modalTriggerRef, setModalTriggerRef] =
useState<RefObject<HTMLButtonElement> | null>(null);

const modalRef = useRef<HTMLDialogElement>(null);

const modalOpen = () => {
const openModal = () => {
setShowModal(true);
setTimeout(() => {
// Focus the dialog element after modal is set to open
modalRef?.current?.focus();
}, 0);
};
const closeModal = () => {
setShowModal(false);
// Focus the original modal trigger button after dialog is closed,
// otherwise, focus will be lost on the page
setTimeout(() => {
modalTriggerRef?.current?.focus();
setModalTriggerRef(null);
}, 0);
};

const addBreadCrumb = (bc) => {
Expand All @@ -36,14 +61,17 @@ export const ApiModalProvider = ({ children }) => {

const value = {
setModalData,
modalOpen,
setModalTriggerRef,
modalTriggerRef,
openModal,
addBreadCrumb,
setBC
};

return (
<TypeContext.Provider value={value}>
<ApiModal
modalRef={modalRef}
data={modalData}
showModal={showModal}
close={closeModal}
Expand Down
54 changes: 43 additions & 11 deletions src/components/ApiDocs/display/ApiModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useCallback } from 'react';
import { Badge, View, Flex, Grid, Card, Button } from '@aws-amplify/ui-react';

import { ApiModalBreadcrumbs } from './ApiModalBreadcrumbs';
Expand All @@ -8,29 +9,52 @@ import { LinkDataType, TypeLinkInterface } from './TypeLink';
import references from '@/directory/apiReferences.json';

interface ApiModalInterface {
data: any;
showModal?: boolean;
close: () => void;
breadCrumbs: LinkDataType[];
clearBC: () => void;
close: () => void;
data: any;
modalRef: React.RefObject<HTMLDialogElement>;
showModal?: boolean;
}

export const ApiModal = ({
data,
showModal = false,
close,
breadCrumbs,
clearBC
clearBC,
close,
data,
modalRef,
showModal = false
}: ApiModalInterface) => {
if (data.type === 'reference') {
data = references[data.target];
}
const description = data?.comment?.summary;

const closeModal = () => {
const closeModal = useCallback(() => {
clearBC();
close();
};
}, [clearBC, close]);

const handleEscape = useCallback(
(event: KeyboardEvent) => {
if (event.key === 'Escape') {
closeModal();
}
},
[closeModal]
);

// Use esc key to close modal
useEffect(() => {
const modal = modalRef.current;
if (showModal && modal) {
window.addEventListener('keyup', handleEscape);

return () => {
window.removeEventListener('keyup', handleEscape);
};
}
}, [showModal, handleEscape, modalRef]);

let name = data.name;
let typeParameters = data.typeArguments;
Expand Down Expand Up @@ -86,13 +110,21 @@ export const ApiModal = ({

return (
<View
aria-label={`${name} API Reference`}
className={`api-modal-container${showModal ? ' api-modal-container--open' : ''}`}
>
<Card as="dialog" className="api-modal" aria-modal="true">
<View className="api-modal-backdrop" onClick={closeModal}></View>
<Card
as="dialog"
aria-label={`${name} API Reference`}
className="api-modal"
aria-modal="true"
ref={modalRef}
tabIndex={-1}
>
<Flex className="api-model__header">
<ApiModalBreadcrumbs items={breadcrumbItems} />
<Button
aria-label={`Close ${name} API Reference modal`}
onClick={closeModal}
size="small"
variation="link"
Expand Down
2 changes: 1 addition & 1 deletion src/components/ApiDocs/display/ApiModalBreadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const ApiModalBreadcrumbs = ({ items }: ApiModalBreadcrumbs) => {
ref={navRef}
aria-label="API Type breadcrumbs"
className="api-modal__breadcrumbs"
tabIndex={0}
tabIndex={items && items.length > 1 ? 0 : -1}
>
{items
? items.map((item, index) => {
Expand Down
21 changes: 16 additions & 5 deletions src/components/ApiDocs/display/TypeLink.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext } from 'react';
import { useContext, useRef } from 'react';
import { TypeContext } from '@/components/ApiDocs/ApiModalProvider';
import { View } from '@aws-amplify/ui-react';

Expand All @@ -15,19 +15,30 @@ export interface TypeLinkInterface {
}

export const TypeLink = ({ linkData, breadCrumbs }: TypeLinkInterface) => {
const { setModalData, modalOpen, addBreadCrumb, setBC } =
useContext(TypeContext);
const {
setModalData,
openModal,
addBreadCrumb,
setBC,
setModalTriggerRef,
modalTriggerRef
} = useContext(TypeContext);
const name = linkData.name;
const className = `type-link kind-${linkData.kind}`;
const btnRef = useRef<HTMLButtonElement>(null);

const onClickHandler = () => {
/** If the modal trigger button hasn't been set, set it here */
if (!modalTriggerRef) {
setModalTriggerRef(btnRef);
}
setModalData(linkData);
if (breadCrumbs) {
setBC(breadCrumbs);
} else {
addBreadCrumb(linkData);
}
modalOpen();
openModal();
};
if (
linkData.type === 'intrinsic' ||
Expand All @@ -36,7 +47,7 @@ export const TypeLink = ({ linkData, breadCrumbs }: TypeLinkInterface) => {
return <View as="span">{linkData.name}</View>;
} else {
return (
<button className={className} onClick={onClickHandler}>
<button className={className} ref={btnRef} onClick={onClickHandler}>
{name}
</button>
);
Expand Down
21 changes: 19 additions & 2 deletions src/styles/reference.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,33 @@
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);

align-items: center;
justify-content: center;
z-index: 99999;
z-index: 5;
&--open {
display: flex;
}
}

.api-modal-backdrop {
position: fixed;
z-index: 1;
background-color: rgba(0, 0, 0, 0.5);
width: 100vw;
height: 100vh;
}

.api-modal {
z-index: 2;
width: 800px;
max-width: 90vw;
max-height: 90vh;
border-radius: var(--amplify-radii-medium);
&:focus-visible {
outline: 2px solid var(--amplify-colors-border-focus);
outline-offset: 2px;
}
}

.api-model__header {
Expand All @@ -44,6 +57,10 @@
overflow: scroll;
align-items: baseline;
gap: 2px;
&:focus-visible {
outline: 2px solid var(--amplify-colors-border-focus);
outline-offset: 2px;
}
}

.api-modal__breadcrumbs__current {
Expand Down