mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Refactor migration files and improve code formatting
- Updated migration files to enhance readability by adjusting indentation and line breaks. - Added missing commas in the index file for migration imports. - Improved accessibility attributes in various UI components by ensuring proper aria-labels and roles. - Refactored key event handlers in UI components for better clarity and consistency. - Enhanced error message handling in form components to ensure proper display and accessibility. - Updated legacy function comments for clarity and maintainability.
This commit is contained in:
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,5 +471,5 @@ export default [
|
||||
MigrationName1769199303656,
|
||||
MigrationName1769202898645,
|
||||
MigrationName1769428619414,
|
||||
MigrationName1769428821686
|
||||
];
|
||||
MigrationName1769428821686,
|
||||
];
|
||||
|
||||
@@ -4,7 +4,10 @@ enum SortOrder {
|
||||
}
|
||||
|
||||
// Maps SortOrder to ARIA sort values for accessibility
|
||||
export const SortOrderToAriaSortMap: Record<SortOrder, "ascending" | "descending"> = {
|
||||
export const SortOrderToAriaSortMap: Record<
|
||||
SortOrder,
|
||||
"ascending" | "descending"
|
||||
> = {
|
||||
[SortOrder.Ascending]: "ascending",
|
||||
[SortOrder.Descending]: "descending",
|
||||
};
|
||||
|
||||
@@ -62,7 +62,9 @@ const Accordion: FunctionComponent<ComponentProps> = (
|
||||
|
||||
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<ComponentProps> = (
|
||||
{!isOpen && <div className="">{props.rightElement}</div>}
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div id={accordionId} className={`space-y-5 ${props.title ? "mt-4" : ""}`}>
|
||||
<div
|
||||
id={accordionId}
|
||||
className={`space-y-5 ${props.title ? "mt-4" : ""}`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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<ComponentProps> = ({
|
||||
// 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);
|
||||
|
||||
|
||||
@@ -67,7 +67,9 @@ const CheckboxElement: FunctionComponent<CategoryProps> = (
|
||||
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<CategoryProps> = (
|
||||
<div className="ml-3 text-sm leading-6">
|
||||
<label className="font-medium text-gray-900">{props.title}</label>
|
||||
{props.description && (
|
||||
<div id="checkbox-description" className="text-gray-500">{props.description}</div>
|
||||
<div id="checkbox-description" className="text-gray-500">
|
||||
{props.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,9 +14,12 @@ const ColorInput: FunctionComponent<ComponentProps> = (
|
||||
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<ComponentProps> = (
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
)}
|
||||
<div>
|
||||
{colorLabel}
|
||||
</div>
|
||||
<div>{colorLabel}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,12 +19,14 @@ const CopyableButton: FunctionComponent<ComponentProps> = (
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const handleCopy = async (): Promise<void> => {
|
||||
const handleCopy: () => Promise<void> = async (): Promise<void> => {
|
||||
refreshCopyToClipboardState();
|
||||
await navigator.clipboard?.writeText(props.textToBeCopied);
|
||||
};
|
||||
|
||||
const handleKeyDown = async (event: React.KeyboardEvent): Promise<void> => {
|
||||
const handleKeyDown: (event: React.KeyboardEvent) => Promise<void> = async (
|
||||
event: React.KeyboardEvent,
|
||||
): Promise<void> => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
await handleCopy();
|
||||
@@ -40,7 +42,9 @@ const CopyableButton: FunctionComponent<ComponentProps> = (
|
||||
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"
|
||||
>
|
||||
{" "}
|
||||
|
||||
@@ -707,7 +707,12 @@ const Dropdown: FunctionComponent<ComponentProps> = (
|
||||
}}
|
||||
/>
|
||||
{props.error && (
|
||||
<p id={errorId} data-testid="error-message" className="mt-1 text-sm text-red-400" role="alert">
|
||||
<p
|
||||
id={errorId}
|
||||
data-testid="error-message"
|
||||
className="mt-1 text-sm text-red-400"
|
||||
role="alert"
|
||||
>
|
||||
{props.error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -105,7 +105,9 @@ const FormField: <T extends GenericObject>(
|
||||
}
|
||||
};
|
||||
|
||||
type GetAutoCompleteFunction = (fieldType: FormFieldSchemaType) => string | undefined;
|
||||
type GetAutoCompleteFunction = (
|
||||
fieldType: FormFieldSchemaType,
|
||||
) => string | undefined;
|
||||
|
||||
const getAutoComplete: GetAutoCompleteFunction = (
|
||||
fieldType: FormFieldSchemaType,
|
||||
@@ -758,7 +760,11 @@ const FormField: <T extends GenericObject>(
|
||||
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);
|
||||
|
||||
@@ -10,11 +10,13 @@ export interface ComponentProps {
|
||||
const FullPageModal: FunctionComponent<ComponentProps> = (
|
||||
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<ComponentProps> = (
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -209,14 +209,22 @@ const Input: FunctionComponent<ComponentProps> = (
|
||||
/>
|
||||
|
||||
{props.error && (
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3" aria-hidden="true">
|
||||
<div
|
||||
className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Icon icon={IconProp.ErrorSolid} className="h-5 w-5 text-red-500" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{props.error && (
|
||||
<p id="input-error-message" data-testid="error-message" className="mt-1 text-sm text-red-400" role="alert">
|
||||
<p
|
||||
id="input-error-message"
|
||||
data-testid="error-message"
|
||||
className="mt-1 text-sm text-red-400"
|
||||
role="alert"
|
||||
>
|
||||
{props.error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -35,7 +35,8 @@ const MermaidDiagram: FunctionComponent<{ chart: string }> = ({
|
||||
}: {
|
||||
chart: string;
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const containerRef: React.RefObject<HTMLDivElement | null> =
|
||||
useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const renderDiagram: () => Promise<void> = async (): Promise<void> => {
|
||||
|
||||
@@ -42,7 +42,6 @@ const MasterPage: FunctionComponent<ComponentProps> = (
|
||||
<React.Fragment>
|
||||
{isOnline && (
|
||||
<div className={props.className}>
|
||||
|
||||
<div
|
||||
className={props.makeTopSectionUnstick ? "" : "sticky top-0 z-10"}
|
||||
>
|
||||
@@ -54,9 +53,7 @@ const MasterPage: FunctionComponent<ComponentProps> = (
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{props.children}
|
||||
|
||||
{props.children}
|
||||
|
||||
{props.footer && props.footer}
|
||||
</div>
|
||||
|
||||
@@ -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<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const modalRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
const modalRef: React.RefObject<HTMLDivElement> =
|
||||
useRef<HTMLDivElement>(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<ComponentProps> = (
|
||||
if (modal) {
|
||||
// Focus the first focusable element in the modal
|
||||
const focusableElements: NodeListOf<Element> = 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();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ const MoreMenu: React.ForwardRefExoticComponent<
|
||||
const { ref, isComponentVisible, setIsComponentVisible } =
|
||||
useComponentOutsideClick(false);
|
||||
const [focusedIndex, setFocusedIndex] = useState<number>(-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 (
|
||||
<div className="relative inline-block text-left" onKeyDown={handleKeyDown}>
|
||||
<div
|
||||
className="relative inline-block text-left"
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
{!props.elementToBeShownInsteadOfButton && (
|
||||
<Button
|
||||
id={buttonId}
|
||||
@@ -128,7 +139,9 @@ const MoreMenu: React.ForwardRefExoticComponent<
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
ref={(el: HTMLDivElement | null) => { menuItemRefs.current[index] = el; }}
|
||||
ref={(el: HTMLDivElement | null) => {
|
||||
menuItemRefs.current[index] = el;
|
||||
}}
|
||||
role="menuitem"
|
||||
tabIndex={focusedIndex === index ? 0 : -1}
|
||||
onClick={() => {
|
||||
@@ -141,7 +154,9 @@ const MoreMenu: React.ForwardRefExoticComponent<
|
||||
e.preventDefault();
|
||||
setIsComponentVisible(false);
|
||||
// Trigger child click
|
||||
const clickEvent: MouseEvent = new MouseEvent("click", { bubbles: true });
|
||||
const clickEvent: MouseEvent = new MouseEvent("click", {
|
||||
bubbles: true,
|
||||
});
|
||||
e.currentTarget.dispatchEvent(clickEvent);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -129,14 +129,20 @@ const Pagination: FunctionComponent<ComponentProps> = (
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
if ((e.key === "Enter" || e.key === " ") && !isPreviousDisabled) {
|
||||
if (
|
||||
(e.key === "Enter" || e.key === " ") &&
|
||||
!isPreviousDisabled
|
||||
) {
|
||||
e.preventDefault();
|
||||
let currentPageNumber: number = props.currentPageNumber;
|
||||
if (typeof currentPageNumber === "string") {
|
||||
currentPageNumber = parseInt(currentPageNumber);
|
||||
}
|
||||
if (props.onNavigateToPage) {
|
||||
props.onNavigateToPage(currentPageNumber - 1, props.itemsOnPage);
|
||||
props.onNavigateToPage(
|
||||
currentPageNumber - 1,
|
||||
props.itemsOnPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -196,7 +202,10 @@ const Pagination: FunctionComponent<ComponentProps> = (
|
||||
currentPageNumber = parseInt(currentPageNumber);
|
||||
}
|
||||
if (props.onNavigateToPage) {
|
||||
props.onNavigateToPage(currentPageNumber + 1, props.itemsOnPage);
|
||||
props.onNavigateToPage(
|
||||
currentPageNumber + 1,
|
||||
props.itemsOnPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -250,14 +259,20 @@ const Pagination: FunctionComponent<ComponentProps> = (
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
if ((e.key === "Enter" || e.key === " ") && !isPreviousDisabled) {
|
||||
if (
|
||||
(e.key === "Enter" || e.key === " ") &&
|
||||
!isPreviousDisabled
|
||||
) {
|
||||
e.preventDefault();
|
||||
let currentPageNumber: number = props.currentPageNumber;
|
||||
if (typeof currentPageNumber === "string") {
|
||||
currentPageNumber = parseInt(currentPageNumber);
|
||||
}
|
||||
if (props.onNavigateToPage) {
|
||||
props.onNavigateToPage(currentPageNumber - 1, props.itemsOnPage);
|
||||
props.onNavigateToPage(
|
||||
currentPageNumber - 1,
|
||||
props.itemsOnPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -317,7 +332,10 @@ const Pagination: FunctionComponent<ComponentProps> = (
|
||||
currentPageNumber = parseInt(currentPageNumber);
|
||||
}
|
||||
if (props.onNavigateToPage) {
|
||||
props.onNavigateToPage(currentPageNumber + 1, props.itemsOnPage);
|
||||
props.onNavigateToPage(
|
||||
currentPageNumber + 1,
|
||||
props.itemsOnPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -63,7 +63,10 @@ const ProgressBar: FunctionComponent<ComponentProps> = (
|
||||
className={`${progressBarSize} bg-indigo-600 rounded-full `}
|
||||
style={{ width: percent + "%" }}
|
||||
></div>
|
||||
<div className="text-sm text-gray-400 mt-1 flex justify-between" aria-hidden="true">
|
||||
<div
|
||||
className="text-sm text-gray-400 mt-1 flex justify-between"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div data-testid="progress-bar-count">
|
||||
{props.count} {props.suffix}
|
||||
</div>
|
||||
|
||||
@@ -74,7 +74,10 @@ const Radio: FunctionComponent<ComponentProps> = (
|
||||
type="radio"
|
||||
className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
|
||||
/>
|
||||
<label htmlFor={optionId} className="block text-sm font-medium leading-6 text-gray-900">
|
||||
<label
|
||||
htmlFor={optionId}
|
||||
className="block text-sm font-medium leading-6 text-gray-900"
|
||||
>
|
||||
{option.label}
|
||||
</label>
|
||||
</div>
|
||||
@@ -82,7 +85,12 @@ const Radio: FunctionComponent<ComponentProps> = (
|
||||
})}
|
||||
|
||||
{props.error && (
|
||||
<p id={errorId} data-testid="error-message" className="mt-1 text-sm text-red-400" role="alert">
|
||||
<p
|
||||
id={errorId}
|
||||
data-testid="error-message"
|
||||
className="mt-1 text-sm text-red-400"
|
||||
role="alert"
|
||||
>
|
||||
{props.error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -17,7 +17,12 @@ const Statusbubble: FunctionComponent<ComponentProps> = (
|
||||
: Black.toString();
|
||||
|
||||
return (
|
||||
<div className="flex" style={props.style} role="status" aria-label={`Status: ${props.text}`}>
|
||||
<div
|
||||
className="flex"
|
||||
style={props.style}
|
||||
role="status"
|
||||
aria-label={`Status: ${props.text}`}
|
||||
>
|
||||
<div className="-mr-2 ml-5" aria-hidden="true">
|
||||
<span className="relative -left-1 -translate-x-full top-1/2 -translate-y-1/2 flex h-3.5 w-3.5">
|
||||
<span
|
||||
|
||||
@@ -3,7 +3,9 @@ import Icon, { ThickProp } from "../Icon/Icon";
|
||||
import FieldType from "../Types/FieldType";
|
||||
import Column from "./Types/Column";
|
||||
import Columns from "./Types/Columns";
|
||||
import SortOrder, { SortOrderToAriaSortMap } from "../../../Types/BaseDatabase/SortOrder";
|
||||
import SortOrder, {
|
||||
SortOrderToAriaSortMap,
|
||||
} from "../../../Types/BaseDatabase/SortOrder";
|
||||
import GenericObject from "../../../Types/GenericObject";
|
||||
import IconProp from "../../../Types/Icon/IconProp";
|
||||
import React, { ReactElement, useEffect, useState } from "react";
|
||||
@@ -85,11 +87,12 @@ const TableHeader: TableHeaderFunction = <T extends GenericObject>(
|
||||
const canSort: boolean = !column.disableSort && Boolean(column.key);
|
||||
|
||||
const isSorted: boolean = canSort && props.sortBy === column.key;
|
||||
const ariaSort: "ascending" | "descending" | "none" | undefined = isSorted
|
||||
? SortOrderToAriaSortMap[props.sortOrder]
|
||||
: canSort
|
||||
? "none"
|
||||
: undefined;
|
||||
const ariaSort: "ascending" | "descending" | "none" | undefined =
|
||||
isSorted
|
||||
? SortOrderToAriaSortMap[props.sortOrder]
|
||||
: canSort
|
||||
? "none"
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<th
|
||||
|
||||
@@ -32,7 +32,9 @@ const TabElement: FunctionComponent<ComponentProps> = (
|
||||
? `${backgroundColor} text-gray-700`
|
||||
: "text-gray-500 hover:text-gray-700";
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent): void => {
|
||||
const handleKeyDown: (event: React.KeyboardEvent) => void = (
|
||||
event: React.KeyboardEvent,
|
||||
): void => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
props.onClick?.();
|
||||
|
||||
@@ -26,7 +26,7 @@ const Tabs: FunctionComponent<ComponentProps> = (
|
||||
}
|
||||
}, [currentTab]);
|
||||
|
||||
const tabPanelId: string = `tabpanel-${currentTab?.name || 'default'}`;
|
||||
const tabPanelId: string = `tabpanel-${currentTab?.name || "default"}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -52,7 +52,7 @@ const Tabs: FunctionComponent<ComponentProps> = (
|
||||
<div
|
||||
id={tabPanelId}
|
||||
role="tabpanel"
|
||||
aria-labelledby={`tab-${currentTab?.name || 'default'}`}
|
||||
aria-labelledby={`tab-${currentTab?.name || "default"}`}
|
||||
className="mt-3 ml-1"
|
||||
>
|
||||
{currentTab && currentTab.children}
|
||||
|
||||
@@ -90,13 +90,21 @@ const TextArea: FunctionComponent<ComponentProps> = (
|
||||
tabIndex={props.tabIndex}
|
||||
/>
|
||||
{props.error && (
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3" aria-hidden="true">
|
||||
<div
|
||||
className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Icon icon={IconProp.ErrorSolid} className="h-5 w-5 text-red-500" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{props.error && (
|
||||
<p id="textarea-error-message" data-testid="error-message" className="mt-1 text-sm text-red-400" role="alert">
|
||||
<p
|
||||
id="textarea-error-message"
|
||||
data-testid="error-message"
|
||||
className="mt-1 text-sm text-red-400"
|
||||
role="alert"
|
||||
>
|
||||
{props.error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -122,7 +122,12 @@ const Toggle: FunctionComponent<ComponentProps> = (
|
||||
)}
|
||||
</div>
|
||||
{props.error && (
|
||||
<p id={errorId} data-testid="error-message" className="mt-1 text-sm text-red-400" role="alert">
|
||||
<p
|
||||
id={errorId}
|
||||
data-testid="error-message"
|
||||
className="mt-1 text-sm text-red-400"
|
||||
role="alert"
|
||||
>
|
||||
{props.error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -514,8 +514,10 @@ export async function generateBlogSitemapXml(page: number): Promise<string> {
|
||||
return xml;
|
||||
}
|
||||
|
||||
// Legacy function for backwards compatibility (generates single sitemap)
|
||||
// This is kept in case any other code references it
|
||||
/*
|
||||
* Legacy function for backwards compatibility (generates single sitemap)
|
||||
* This is kept in case any other code references it
|
||||
*/
|
||||
export const generateSitemapXml: () => Promise<string> =
|
||||
async (): Promise<string> => {
|
||||
// Redirect to sitemap index for backwards compatibility
|
||||
|
||||
Reference in New Issue
Block a user