diff --git a/apps/web/src/app/global-error.tsx b/apps/web/src/app/global-error.tsx
index 69c9f3f..fb7320c 100644
--- a/apps/web/src/app/global-error.tsx
+++ b/apps/web/src/app/global-error.tsx
@@ -2,19 +2,13 @@
import { useEffect } from "react";
-export default function GlobalError({
- error,
- reset,
-}: {
- error: Error;
- reset: () => void;
-}) {
+export default function GlobalError({ error, reset }: { error: Error; reset: () => void }) {
useEffect(() => {
console.error(error);
}, [error]);
return (
-
+
Something went wrong
diff --git a/apps/web/src/components/ui/AnimatedModal.tsx b/apps/web/src/components/ui/AnimatedModal.tsx
index bcb7bc9..cdf9313 100644
--- a/apps/web/src/components/ui/AnimatedModal.tsx
+++ b/apps/web/src/components/ui/AnimatedModal.tsx
@@ -12,6 +12,8 @@ interface AnimatedModalProps {
overlayClassName?: string;
maxWidth?: string;
disableBackdropClose?: boolean;
+ /** ID of the element that labels this dialog (for aria-labelledby). */
+ ariaLabelledBy?: string;
}
export function AnimatedModal({
@@ -22,6 +24,7 @@ export function AnimatedModal({
overlayClassName,
maxWidth = "max-w-xl",
disableBackdropClose = false,
+ ariaLabelledBy,
}: AnimatedModalProps) {
const panelRef = useRef(null);
@@ -50,10 +53,7 @@ export function AnimatedModal({
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
- className={
- overlayClassName ??
- "absolute inset-0 bg-black/40 backdrop-blur-sm"
- }
+ className={overlayClassName ?? "absolute inset-0 bg-black/40 backdrop-blur-sm"}
onClick={disableBackdropClose ? undefined : onClose}
aria-hidden="true"
/>
@@ -63,6 +63,7 @@ export function AnimatedModal({
ref={panelRef}
role="dialog"
aria-modal="true"
+ aria-labelledby={ariaLabelledBy}
initial={{ opacity: 0, scale: 0.97 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.97 }}
diff --git a/apps/web/src/components/ui/EntityCombobox.tsx b/apps/web/src/components/ui/EntityCombobox.tsx
index 7bcd73f..a7d5aed 100644
--- a/apps/web/src/components/ui/EntityCombobox.tsx
+++ b/apps/web/src/components/ui/EntityCombobox.tsx
@@ -1,7 +1,7 @@
"use client";
import { createPortal } from "react-dom";
-import { useState, useRef, useMemo, useCallback, type ReactNode } from "react";
+import { useState, useRef, useMemo, useCallback, useId, type ReactNode } from "react";
import { useDebounce } from "~/hooks/useDebounce.js";
import { useAnchoredOverlay } from "~/hooks/useAnchoredOverlay.js";
@@ -37,6 +37,7 @@ export function EntityCombobox({
const debouncedSearch = useDebounce(search, 300);
const containerRef = useRef(null);
const inputRef = useRef(null);
+ const listboxId = useId();
const closeDropdown = useCallback(() => {
setOpen(false);
setSearch("");
@@ -82,6 +83,10 @@ export function EntityCombobox({
setSearch(e.target.value)}
onFocus={handleFocus}
@@ -97,7 +102,11 @@ export function EntityCombobox({
{value && !disabled && !open && (
- {open && (
- typeof document !== "undefined"
+ {open &&
+ (typeof document !== "undefined"
? createPortal(
({
width: position.minWidth,
}}
>
-
+
{items.length === 0 ? (
- - No results
+ -
+ No results
+
) : (
items.map((item) => (
- -
+
-
,
document.body,
)
- : null
- )}
+ : null)}
);
}
diff --git a/apps/web/src/components/ui/FilterBar.tsx b/apps/web/src/components/ui/FilterBar.tsx
index 843e359..2022492 100644
--- a/apps/web/src/components/ui/FilterBar.tsx
+++ b/apps/web/src/components/ui/FilterBar.tsx
@@ -8,7 +8,11 @@ interface FilterBarProps {
export function FilterBar({ children, hasActiveFilters, onClearFilters }: FilterBarProps) {
return (
-
+
{children}
{hasActiveFilters && onClearFilters && (