diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.ts index cb6d5fc128..9782d9f54f 100644 --- a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.ts +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1769428619414-MigrationName.ts @@ -1,20 +1,35 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1769428619414 implements MigrationInterface { - public name = 'MigrationName1769428619414' + public name = "MigrationName1769428619414"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "AlertEpisode" ADD "titleTemplate" character varying(100)`); - await queryRunner.query(`ALTER TABLE "AlertEpisode" ADD "descriptionTemplate" character varying`); - await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`); - await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`); - await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`); - await queryRunner.query(`ALTER TABLE "AlertEpisode" DROP COLUMN "descriptionTemplate"`); - await queryRunner.query(`ALTER TABLE "AlertEpisode" DROP COLUMN "titleTemplate"`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "AlertEpisode" ADD "titleTemplate" character varying(100)`, + ); + await queryRunner.query( + `ALTER TABLE "AlertEpisode" ADD "descriptionTemplate" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`, + ); + await queryRunner.query( + `ALTER TABLE "AlertEpisode" DROP COLUMN "descriptionTemplate"`, + ); + await queryRunner.query( + `ALTER TABLE "AlertEpisode" DROP COLUMN "titleTemplate"`, + ); + } } diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.ts index edab1fb515..6990624d5b 100644 --- a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.ts +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1769428821686-MigrationName.ts @@ -1,24 +1,47 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class MigrationName1769428821686 implements MigrationInterface { - public name = 'MigrationName1769428821686' + public name = "MigrationName1769428821686"; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "AlertGroupingRule" DROP COLUMN "episodeTitleTemplate"`); - await queryRunner.query(`ALTER TABLE "AlertGroupingRule" ADD "episodeTitleTemplate" character varying`); - await queryRunner.query(`ALTER TABLE "AlertEpisode" DROP COLUMN "titleTemplate"`); - await queryRunner.query(`ALTER TABLE "AlertEpisode" ADD "titleTemplate" character varying`); - await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`); - await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`); - await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`); - await queryRunner.query(`ALTER TABLE "AlertEpisode" DROP COLUMN "titleTemplate"`); - await queryRunner.query(`ALTER TABLE "AlertEpisode" ADD "titleTemplate" character varying(100)`); - await queryRunner.query(`ALTER TABLE "AlertGroupingRule" DROP COLUMN "episodeTitleTemplate"`); - await queryRunner.query(`ALTER TABLE "AlertGroupingRule" ADD "episodeTitleTemplate" character varying(100)`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "AlertGroupingRule" DROP COLUMN "episodeTitleTemplate"`, + ); + await queryRunner.query( + `ALTER TABLE "AlertGroupingRule" ADD "episodeTitleTemplate" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "AlertEpisode" DROP COLUMN "titleTemplate"`, + ); + await queryRunner.query( + `ALTER TABLE "AlertEpisode" ADD "titleTemplate" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`, + ); + await queryRunner.query( + `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`, + ); + await queryRunner.query( + `ALTER TABLE "AlertEpisode" DROP COLUMN "titleTemplate"`, + ); + await queryRunner.query( + `ALTER TABLE "AlertEpisode" ADD "titleTemplate" character varying(100)`, + ); + await queryRunner.query( + `ALTER TABLE "AlertGroupingRule" DROP COLUMN "episodeTitleTemplate"`, + ); + await queryRunner.query( + `ALTER TABLE "AlertGroupingRule" ADD "episodeTitleTemplate" character varying(100)`, + ); + } } diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts index f7b1bc5480..4235230995 100644 --- a/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts @@ -471,5 +471,5 @@ export default [ MigrationName1769199303656, MigrationName1769202898645, MigrationName1769428619414, - MigrationName1769428821686 -]; \ No newline at end of file + MigrationName1769428821686, +]; diff --git a/Common/Types/BaseDatabase/SortOrder.ts b/Common/Types/BaseDatabase/SortOrder.ts index f6e0e44028..e6fd5e4606 100644 --- a/Common/Types/BaseDatabase/SortOrder.ts +++ b/Common/Types/BaseDatabase/SortOrder.ts @@ -4,7 +4,10 @@ enum SortOrder { } // Maps SortOrder to ARIA sort values for accessibility -export const SortOrderToAriaSortMap: Record = { +export const SortOrderToAriaSortMap: Record< + SortOrder, + "ascending" | "descending" +> = { [SortOrder.Ascending]: "ascending", [SortOrder.Descending]: "descending", }; diff --git a/Common/UI/Components/Accordion/Accordion.tsx b/Common/UI/Components/Accordion/Accordion.tsx index afea36bad2..cea4060491 100644 --- a/Common/UI/Components/Accordion/Accordion.tsx +++ b/Common/UI/Components/Accordion/Accordion.tsx @@ -62,7 +62,9 @@ const Accordion: FunctionComponent = ( const accordionId: string = `accordion-content-${React.useId()}`; - const handleKeyDown = (event: React.KeyboardEvent): void => { + const handleKeyDown: (event: React.KeyboardEvent) => void = ( + event: React.KeyboardEvent, + ): void => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); setIsOpen(!isOpen); @@ -122,7 +124,10 @@ const Accordion: FunctionComponent = ( {!isOpen &&
{props.rightElement}
} {isOpen && ( -
+
{props.children}
)} diff --git a/Common/UI/Components/Button/Button.tsx b/Common/UI/Components/Button/Button.tsx index ea6fa09476..de2250a2a3 100644 --- a/Common/UI/Components/Button/Button.tsx +++ b/Common/UI/Components/Button/Button.tsx @@ -53,7 +53,15 @@ export interface ComponentProps { tooltip?: string | undefined; ariaLabel?: string | undefined; ariaExpanded?: boolean | undefined; - ariaHaspopup?: "menu" | "listbox" | "dialog" | "tree" | "grid" | "true" | "false" | undefined; + ariaHaspopup?: + | "menu" + | "listbox" + | "dialog" + | "tree" + | "grid" + | "true" + | "false" + | undefined; ariaControls?: string | undefined; } @@ -244,7 +252,8 @@ const Button: FunctionComponent = ({ // For icon-only buttons, use title as aria-label for accessibility const computedAriaLabel: string | undefined = ariaLabel || - (buttonStyle === ButtonStyleType.ICON || buttonStyle === ButtonStyleType.ICON_LIGHT + (buttonStyle === ButtonStyleType.ICON || + buttonStyle === ButtonStyleType.ICON_LIGHT ? title || tooltip : undefined); diff --git a/Common/UI/Components/Checkbox/Checkbox.tsx b/Common/UI/Components/Checkbox/Checkbox.tsx index 28f8a40f8a..d29170c185 100644 --- a/Common/UI/Components/Checkbox/Checkbox.tsx +++ b/Common/UI/Components/Checkbox/Checkbox.tsx @@ -67,7 +67,9 @@ const CheckboxElement: FunctionComponent = ( onFocus={props.onFocus} onBlur={props.onBlur} data-testid={props.dataTestId} - aria-describedby={props.description ? "checkbox-description" : undefined} + aria-describedby={ + props.description ? "checkbox-description" : undefined + } aria-invalid={props.error ? "true" : undefined} type="checkbox" className={`accent-indigo-600 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600 ${ @@ -78,7 +80,9 @@ const CheckboxElement: FunctionComponent = (
{props.description && ( -
{props.description}
+
+ {props.description} +
)}
diff --git a/Common/UI/Components/ColorViewer/ColorViewer.tsx b/Common/UI/Components/ColorViewer/ColorViewer.tsx index 7735bc600a..53baa97799 100644 --- a/Common/UI/Components/ColorViewer/ColorViewer.tsx +++ b/Common/UI/Components/ColorViewer/ColorViewer.tsx @@ -14,9 +14,12 @@ const ColorInput: FunctionComponent = ( props: ComponentProps, ): ReactElement => { const hasOnClick: boolean = Boolean(props.onClick); - const colorLabel: string = props.value?.toString() || props.placeholder || "No Color Selected"; + const colorLabel: string = + props.value?.toString() || props.placeholder || "No Color Selected"; - const handleKeyDown = (event: React.KeyboardEvent): void => { + const handleKeyDown: (event: React.KeyboardEvent) => void = ( + event: React.KeyboardEvent, + ): void => { if (hasOnClick && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); props.onClick?.(); @@ -51,9 +54,7 @@ const ColorInput: FunctionComponent = ( aria-hidden="true" > )} -
- {colorLabel} -
+
{colorLabel}
); }; diff --git a/Common/UI/Components/CopyableButton/CopyableButton.tsx b/Common/UI/Components/CopyableButton/CopyableButton.tsx index 7b4d182e58..9d176230d5 100644 --- a/Common/UI/Components/CopyableButton/CopyableButton.tsx +++ b/Common/UI/Components/CopyableButton/CopyableButton.tsx @@ -19,12 +19,14 @@ const CopyableButton: FunctionComponent = ( }, 2000); }; - const handleCopy = async (): Promise => { + const handleCopy: () => Promise = async (): Promise => { refreshCopyToClipboardState(); await navigator.clipboard?.writeText(props.textToBeCopied); }; - const handleKeyDown = async (event: React.KeyboardEvent): Promise => { + const handleKeyDown: (event: React.KeyboardEvent) => Promise = async ( + event: React.KeyboardEvent, + ): Promise => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); await handleCopy(); @@ -40,7 +42,9 @@ const CopyableButton: FunctionComponent = ( onKeyDown={handleKeyDown} role="button" tabIndex={0} - aria-label={copiedToClipboard ? "Copied to clipboard" : "Copy to clipboard"} + aria-label={ + copiedToClipboard ? "Copied to clipboard" : "Copy to clipboard" + } aria-live="polite" > {" "} diff --git a/Common/UI/Components/Dropdown/Dropdown.tsx b/Common/UI/Components/Dropdown/Dropdown.tsx index 42bc63cbc7..709f974611 100644 --- a/Common/UI/Components/Dropdown/Dropdown.tsx +++ b/Common/UI/Components/Dropdown/Dropdown.tsx @@ -707,7 +707,12 @@ const Dropdown: FunctionComponent = ( }} /> {props.error && ( - )} diff --git a/Common/UI/Components/Forms/Fields/FormField.tsx b/Common/UI/Components/Forms/Fields/FormField.tsx index 251bdeb136..ff35534034 100644 --- a/Common/UI/Components/Forms/Fields/FormField.tsx +++ b/Common/UI/Components/Forms/Fields/FormField.tsx @@ -105,7 +105,9 @@ const FormField: ( } }; - type GetAutoCompleteFunction = (fieldType: FormFieldSchemaType) => string | undefined; + type GetAutoCompleteFunction = ( + fieldType: FormFieldSchemaType, + ) => string | undefined; const getAutoComplete: GetAutoCompleteFunction = ( fieldType: FormFieldSchemaType, @@ -758,7 +760,11 @@ const FormField: ( error={props.touched && props.error ? props.error : undefined} dataTestId={props.field.dataTestId} type={fieldType as InputType} - autoComplete={props.field.fieldType ? getAutoComplete(props.field.fieldType) : undefined} + autoComplete={ + props.field.fieldType + ? getAutoComplete(props.field.fieldType) + : undefined + } onChange={(value: string) => { onChange(value); props.setFieldValue(props.fieldName, value); diff --git a/Common/UI/Components/FullPageModal/FullPageModal.tsx b/Common/UI/Components/FullPageModal/FullPageModal.tsx index 63082fc09f..8ac44809bc 100644 --- a/Common/UI/Components/FullPageModal/FullPageModal.tsx +++ b/Common/UI/Components/FullPageModal/FullPageModal.tsx @@ -10,11 +10,13 @@ export interface ComponentProps { const FullPageModal: FunctionComponent = ( props: ComponentProps, ): ReactElement => { - const handleClose = (): void => { + const handleClose: () => void = (): void => { props.onClose?.(); }; - const handleKeyDown = (event: React.KeyboardEvent): void => { + const handleKeyDown: (event: React.KeyboardEvent) => void = ( + event: React.KeyboardEvent, + ): void => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); handleClose(); @@ -23,7 +25,9 @@ const FullPageModal: FunctionComponent = ( // Handle Escape key at the modal level React.useEffect(() => { - const handleEscapeKey = (event: KeyboardEvent): void => { + const handleEscapeKey: (event: KeyboardEvent) => void = ( + event: KeyboardEvent, + ): void => { if (event.key === "Escape") { handleClose(); } diff --git a/Common/UI/Components/Input/Input.tsx b/Common/UI/Components/Input/Input.tsx index 9c39e7de29..7518e9858a 100644 --- a/Common/UI/Components/Input/Input.tsx +++ b/Common/UI/Components/Input/Input.tsx @@ -209,14 +209,22 @@ const Input: FunctionComponent = ( /> {props.error && ( - {props.error && ( - )} diff --git a/Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx b/Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx index 01d67de246..7fe3235338 100644 --- a/Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx +++ b/Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx @@ -35,7 +35,8 @@ const MermaidDiagram: FunctionComponent<{ chart: string }> = ({ }: { chart: string; }) => { - const containerRef = useRef(null); + const containerRef: React.RefObject = + useRef(null); useEffect(() => { const renderDiagram: () => Promise = async (): Promise => { diff --git a/Common/UI/Components/MasterPage/MasterPage.tsx b/Common/UI/Components/MasterPage/MasterPage.tsx index 56c51cc823..ee4f017448 100644 --- a/Common/UI/Components/MasterPage/MasterPage.tsx +++ b/Common/UI/Components/MasterPage/MasterPage.tsx @@ -42,7 +42,6 @@ const MasterPage: FunctionComponent = ( {isOnline && (
-
@@ -54,9 +53,7 @@ const MasterPage: FunctionComponent = ( />
- - {props.children} - + {props.children} {props.footer && props.footer}
diff --git a/Common/UI/Components/Modal/Modal.tsx b/Common/UI/Components/Modal/Modal.tsx index cd1530ddb2..4686c1c138 100644 --- a/Common/UI/Components/Modal/Modal.tsx +++ b/Common/UI/Components/Modal/Modal.tsx @@ -6,7 +6,12 @@ import ModalBody from "./ModalBody"; import ModalFooter from "./ModalFooter"; import { VeryLightGray } from "../../../Types/BrandColors"; import IconProp from "../../../Types/Icon/IconProp"; -import React, { FunctionComponent, ReactElement, useEffect, useRef } from "react"; +import React, { + FunctionComponent, + ReactElement, + useEffect, + useRef, +} from "react"; export enum ModalWidth { Normal, @@ -38,11 +43,14 @@ export interface ComponentProps { const Modal: FunctionComponent = ( props: ComponentProps, ): ReactElement => { - const modalRef: React.RefObject = useRef(null); + const modalRef: React.RefObject = + useRef(null); // Handle Escape key to close modal useEffect(() => { - const handleEscapeKey: (event: KeyboardEvent) => void = (event: KeyboardEvent): void => { + const handleEscapeKey: (event: KeyboardEvent) => void = ( + event: KeyboardEvent, + ): void => { if (event.key === "Escape" && props.onClose) { props.onClose(); } @@ -60,9 +68,11 @@ const Modal: FunctionComponent = ( if (modal) { // Focus the first focusable element in the modal const focusableElements: NodeListOf = modal.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ); - const firstFocusable: HTMLElement | undefined = focusableElements[0] as HTMLElement | undefined; + const firstFocusable: HTMLElement | undefined = focusableElements[0] as + | HTMLElement + | undefined; if (firstFocusable) { firstFocusable.focus(); } diff --git a/Common/UI/Components/MoreMenu/MoreMenu.tsx b/Common/UI/Components/MoreMenu/MoreMenu.tsx index 0ace3d7813..608bf51646 100644 --- a/Common/UI/Components/MoreMenu/MoreMenu.tsx +++ b/Common/UI/Components/MoreMenu/MoreMenu.tsx @@ -29,7 +29,8 @@ const MoreMenu: React.ForwardRefExoticComponent< const { ref, isComponentVisible, setIsComponentVisible } = useComponentOutsideClick(false); const [focusedIndex, setFocusedIndex] = useState(-1); - const menuItemRefs: React.MutableRefObject<(HTMLDivElement | null)[]> = useRef<(HTMLDivElement | null)[]>([]); + const menuItemRefs: React.MutableRefObject<(HTMLDivElement | null)[]> = + useRef<(HTMLDivElement | null)[]>([]); useImperativeHandle(componentRef, () => { return { @@ -62,39 +63,49 @@ const MoreMenu: React.ForwardRefExoticComponent< } }, [focusedIndex]); - const handleKeyDown: (event: React.KeyboardEvent) => void = useCallback((event: React.KeyboardEvent): void => { - if (!isComponentVisible) { - return; - } + const handleKeyDown: (event: React.KeyboardEvent) => void = useCallback( + (event: React.KeyboardEvent): void => { + if (!isComponentVisible) { + return; + } - const itemCount: number = props.children.length; + const itemCount: number = props.children.length; - switch (event.key) { - case "Escape": - event.preventDefault(); - setIsComponentVisible(false); - break; - case "ArrowDown": - event.preventDefault(); - setFocusedIndex((prev: number) => (prev + 1) % itemCount); - break; - case "ArrowUp": - event.preventDefault(); - setFocusedIndex((prev: number) => (prev - 1 + itemCount) % itemCount); - break; - case "Home": - event.preventDefault(); - setFocusedIndex(0); - break; - case "End": - event.preventDefault(); - setFocusedIndex(itemCount - 1); - break; - } - }, [isComponentVisible, props.children.length, setIsComponentVisible]); + switch (event.key) { + case "Escape": + event.preventDefault(); + setIsComponentVisible(false); + break; + case "ArrowDown": + event.preventDefault(); + setFocusedIndex((prev: number) => { + return (prev + 1) % itemCount; + }); + break; + case "ArrowUp": + event.preventDefault(); + setFocusedIndex((prev: number) => { + return (prev - 1 + itemCount) % itemCount; + }); + break; + case "Home": + event.preventDefault(); + setFocusedIndex(0); + break; + case "End": + event.preventDefault(); + setFocusedIndex(itemCount - 1); + break; + } + }, + [isComponentVisible, props.children.length, setIsComponentVisible], + ); return ( -
+
{!props.elementToBeShownInsteadOfButton && (
-