Accessibility features (#14518)

* Add screen reader aria labels to buttons and menu items

* Fix sub_label score in search detail dialog
This commit is contained in:
Josh Hawkins 2024-10-22 17:07:42 -05:00 committed by GitHub
parent c7d9f83638
commit ad308252a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 358 additions and 115 deletions

View File

@ -121,6 +121,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
variant="select"
disabled={isLoading}
className="flex flex-1"
aria-label="Login"
>
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
Login

View File

@ -46,6 +46,7 @@ export function DownloadVideoButton({
disabled={isDownloading}
className="flex items-center gap-2"
size="sm"
aria-label="Download Video"
>
<a
href={source}

View File

@ -55,7 +55,12 @@ export default function DebugCameraImage({
searchParams={searchParams}
cameraClasses="relative w-full h-full flex justify-center"
/>
<Button onClick={handleToggleSettings} variant="link" size="sm">
<Button
onClick={handleToggleSettings}
variant="link"
size="sm"
aria-label="Settings"
>
<span className="h-5 w-5">
<LuSettings />
</span>{" "}

View File

@ -121,6 +121,7 @@ export function AnimatedEventCard({
<Button
className="absolute right-2 top-1 z-40 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
size="xs"
aria-label="Mark as Reviewed"
onClick={async () => {
await axios.post(`reviews/viewed`, { ids: [event.id] });
updateEvents();

View File

@ -113,6 +113,7 @@ export default function ExportCard({
/>
<DialogFooter>
<Button
aria-label="Save Export"
size="sm"
variant="select"
disabled={(editName?.update?.length ?? 0) == 0}
@ -206,6 +207,7 @@ export default function ExportCard({
{!exportedRecording.in_progress && (
<Button
className="absolute left-1/2 top-1/2 h-20 w-20 -translate-x-1/2 -translate-y-1/2 cursor-pointer text-white hover:bg-transparent hover:text-white"
aria-label="Play"
variant="ghost"
onClick={() => {
onSelect(exportedRecording);

View File

@ -36,6 +36,7 @@ export default function NewReviewData({
: "invisible",
"mx-auto mt-5 bg-gray-400 text-center text-white",
)}
aria-label="View new review items"
onClick={() => {
pullLatestData();
if (contentRef.current) {

View File

@ -34,6 +34,7 @@ export default function CalendarFilterButton({
const trigger = (
<Button
className="flex items-center gap-2"
aria-label="Select a date to filter by"
variant={day == undefined ? "default" : "select"}
size="sm"
>
@ -57,6 +58,7 @@ export default function CalendarFilterButton({
<DropdownMenuSeparator />
<div className="flex items-center justify-center p-2">
<Button
aria-label="Reset"
onClick={() => {
updateSelectedDay(undefined);
}}
@ -99,6 +101,7 @@ export function CalendarRangeFilterButton({
const trigger = (
<Button
className="flex items-center gap-2"
aria-label="Select a date to filter by"
variant={range == undefined ? "default" : "select"}
size="sm"
>

View File

@ -141,6 +141,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
: "bg-secondary text-secondary-foreground focus:bg-secondary focus:text-secondary-foreground"
}
aria-label="All Cameras"
size="xs"
onClick={() => (group ? setGroup("default", true) : null)}
onMouseEnter={() => (isDesktop ? showTooltip("default") : null)}
@ -165,6 +166,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
? "bg-blue-900 bg-opacity-60 text-selected focus:bg-blue-900 focus:bg-opacity-60"
: "bg-secondary text-secondary-foreground"
}
aria-label="Camera Group"
size="xs"
onClick={() => setGroup(name, group != "default")}
onMouseEnter={() => (isDesktop ? showTooltip(name) : null)}
@ -191,6 +193,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
<Button
className="bg-secondary text-muted-foreground"
aria-label="Add camera group"
size="xs"
onClick={() => setAddGroup(true)}
>
@ -355,6 +358,7 @@ function NewGroupDialog({
"size-6 rounded-md bg-secondary-foreground p-1 text-background",
isMobile && "text-secondary-foreground",
)}
aria-label="Add camera group"
onClick={() => {
setEditState("add");
}}
@ -536,10 +540,16 @@ export function CameraGroupRow({
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem onClick={onEditGroup}>
<DropdownMenuItem
aria-label="Edit group"
onClick={onEditGroup}
>
Edit
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setDeleteDialogOpen(true)}>
<DropdownMenuItem
aria-label="Delete group"
onClick={() => setDeleteDialogOpen(true)}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
@ -793,13 +803,19 @@ export function CameraGroupEdit({
<Separator className="my-2 flex bg-secondary" />
<div className="flex flex-row gap-2 py-5 md:pb-0">
<Button type="button" className="flex flex-1" onClick={onCancel}>
<Button
type="button"
className="flex flex-1"
aria-label="Cancel"
onClick={onCancel}
>
Cancel
</Button>
<Button
variant="select"
disabled={isLoading}
className="flex flex-1"
aria-label="Save"
type="submit"
>
{isLoading ? (

View File

@ -55,6 +55,7 @@ export function CamerasFilterButton({
const trigger = (
<Button
className="flex items-center gap-2 capitalize"
aria-label="Cameras Filter"
variant={selectedCameras?.length == undefined ? "default" : "select"}
size="sm"
>
@ -202,6 +203,7 @@ export function CamerasFilterContent({
<DropdownMenuSeparator />
<div className="flex items-center justify-evenly p-2">
<Button
aria-label="Apply"
variant="select"
disabled={currentCameras?.length === 0}
onClick={() => {
@ -212,6 +214,7 @@ export function CamerasFilterContent({
Apply
</Button>
<Button
aria-label="Reset"
onClick={() => {
setCurrentCameras(undefined);
updateCameraFilter(undefined);

View File

@ -17,7 +17,11 @@ export function LogLevelFilterButton({
updateLabelFilter,
}: LogLevelFilterButtonProps) {
const trigger = (
<Button size="sm" className="flex items-center gap-2">
<Button
size="sm"
className="flex items-center gap-2"
aria-label="Filter log level"
>
<FaFilter className="text-secondary-foreground" />
<div className="hidden text-primary md:block">Filter</div>
</Button>

View File

@ -104,6 +104,7 @@ export default function ReviewActionGroup({
{selectedReviews.length == 1 && (
<Button
className="flex items-center gap-2 p-2"
aria-label="Export"
size="sm"
onClick={() => {
onExport(selectedReviews[0]);
@ -116,6 +117,7 @@ export default function ReviewActionGroup({
)}
<Button
className="flex items-center gap-2 p-2"
aria-label="Mark as reviewed"
size="sm"
onClick={onMarkAsReviewed}
>
@ -124,6 +126,7 @@ export default function ReviewActionGroup({
</Button>
<Button
className="flex items-center gap-2 p-2"
aria-label="Delete"
size="sm"
onClick={handleDelete}
>

View File

@ -278,6 +278,7 @@ function ShowReviewFilter({
<Button
className="block duration-0 md:hidden"
aria-label="Show reviewed"
variant={showReviewedSwitch ? "select" : "default"}
size="sm"
onClick={() =>
@ -338,6 +339,7 @@ function GeneralFilterButton({
selectedLabels?.length || selectedZones?.length ? "select" : "default"
}
className="flex items-center gap-2 capitalize"
aria-label="Filter"
>
<FaFilter
className={`${selectedLabels?.length || selectedZones?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
@ -538,6 +540,7 @@ export function GeneralFilterContent({
<DropdownMenuSeparator />
<div className="flex items-center justify-evenly p-2">
<Button
aria-label="Apply"
variant="select"
onClick={() => {
if (selectedLabels != currentLabels) {
@ -554,6 +557,7 @@ export function GeneralFilterContent({
Apply
</Button>
<Button
aria-label="Reset"
onClick={() => {
setCurrentLabels(undefined);
setCurrentZones?.(undefined);
@ -601,6 +605,7 @@ function ShowMotionOnlyButton({
<Button
size="sm"
className="duration-0"
aria-label="Show Motion Only"
variant={motionOnlyButton ? "select" : "default"}
onClick={() => setMotionOnlyButton(!motionOnlyButton)}
>

View File

@ -227,6 +227,7 @@ function GeneralFilterButton({
size="sm"
variant={selectedLabels?.length ? "select" : "default"}
className="flex items-center gap-2 capitalize"
aria-label="Labels"
>
<MdLabel
className={`${selectedLabels?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
@ -336,6 +337,7 @@ export function GeneralFilterContent({
<DropdownMenuSeparator />
<div className="flex items-center justify-evenly p-2">
<Button
aria-label="Apply"
variant="select"
onClick={() => {
if (selectedLabels != currentLabels) {
@ -348,6 +350,7 @@ export function GeneralFilterContent({
Apply
</Button>
<Button
aria-label="Reset"
onClick={() => {
setCurrentLabels(undefined);
updateLabelFilter(undefined);

View File

@ -21,6 +21,7 @@ export function ZoneMaskFilterButton({
size="sm"
variant={selectedZoneMask?.length ? "select" : "default"}
className="flex items-center gap-2 capitalize"
aria-label="Filter by zone mask"
>
<FaFilter
className={`${selectedZoneMask?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}

View File

@ -66,7 +66,10 @@ export default function IconPicker({
>
<PopoverTrigger asChild>
{!selectedIcon?.name || !selectedIcon?.Icon ? (
<Button className="mt-2 w-full text-muted-foreground">
<Button
className="mt-2 w-full text-muted-foreground"
aria-label="Select an icon"
>
Select an icon
</Button>
) : (

View File

@ -59,11 +59,14 @@ export function SaveSearchDialog({
placeholder="Enter a name for your search"
/>
<DialogFooter>
<Button onClick={onClose}>Cancel</Button>
<Button aria-label="Cancel" onClick={onClose}>
Cancel
</Button>
<Button
onClick={handleSave}
variant="select"
className="mb-2 md:mb-0"
aria-label="Save this search"
>
Save
</Button>

View File

@ -72,6 +72,7 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
className={
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
}
aria-label="Log out"
>
<a className="flex" href={logoutUrl}>
<LuLogOut className="mr-2 size-4" />

View File

@ -176,6 +176,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex items-center p-2 text-sm"
}
aria-label="Log out"
>
<a className="flex" href={logoutUrl}>
<LuLogOut className="mr-2 size-4" />
@ -194,6 +195,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex w-full items-center p-2 text-sm"
}
aria-label="System metrics"
>
<LuActivity className="mr-2 size-4" />
<span>System metrics</span>
@ -206,6 +208,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex w-full items-center p-2 text-sm"
}
aria-label="System logs"
>
<LuList className="mr-2 size-4" />
<span>System logs</span>
@ -224,6 +227,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex w-full items-center p-2 text-sm"
}
aria-label="Settings"
>
<LuSettings className="mr-2 size-4" />
<span>Settings</span>
@ -236,6 +240,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex w-full items-center p-2 text-sm"
}
aria-label="Configuration editor"
>
<LuPenSquare className="mr-2 size-4" />
<span>Configuration editor</span>
@ -269,6 +274,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex items-center p-2 text-sm"
}
aria-label="Light mode"
onClick={() => setTheme("light")}
>
{theme === "light" ? (
@ -286,6 +292,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex items-center p-2 text-sm"
}
aria-label="Dark mode"
onClick={() => setTheme("dark")}
>
{theme === "dark" ? (
@ -303,6 +310,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex items-center p-2 text-sm"
}
aria-label="Use the system settings for light or dark mode"
onClick={() => setTheme("system")}
>
{theme === "system" ? (
@ -343,6 +351,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
? "cursor-pointer"
: "flex items-center p-2 text-sm"
}
aria-label={`Color scheme - ${scheme}`}
onClick={() => setColorScheme(scheme)}
>
{scheme === colorScheme ? (
@ -370,6 +379,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
}
aria-label="Frigate documentation"
>
<LuLifeBuoy className="mr-2 size-4" />
<span>Documentation</span>
@ -383,6 +393,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
}
aria-label="Frigate Github"
>
<LuGithub className="mr-2 size-4" />
<span>GitHub</span>
@ -393,6 +404,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
className={
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
}
aria-label="Restart Frigate"
onClick={() => setRestartDialogOpen(true)}
>
<LuRotateCw className="mr-2 size-4" />
@ -446,7 +458,12 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
<p>This page will reload in {countdown} seconds.</p>
</SheetDescription>
</SheetHeader>
<Button size="lg" className="mt-5" onClick={handleForceReload}>
<Button
size="lg"
className="mt-5"
aria-label="Force reload now"
onClick={handleForceReload}
>
Force Reload Now
</Button>
</div>

View File

@ -86,7 +86,7 @@ export default function SearchResultActions({
const menuItems = (
<>
{searchResult.has_clip && (
<MenuItem>
<MenuItem aria-label="Download video">
<a
className="flex items-center"
href={`${baseUrl}api/events/${searchResult.id}/clip.mp4`}
@ -98,7 +98,7 @@ export default function SearchResultActions({
</MenuItem>
)}
{searchResult.has_snapshot && (
<MenuItem>
<MenuItem aria-label="Download snapshot">
<a
className="flex items-center"
href={`${baseUrl}api/events/${searchResult.id}/snapshot.jpg`}
@ -109,12 +109,18 @@ export default function SearchResultActions({
</a>
</MenuItem>
)}
<MenuItem onClick={showObjectLifecycle}>
<MenuItem
aria-label="Show the object lifecycle"
onClick={showObjectLifecycle}
>
<FaArrowsRotate className="mr-2 size-4" />
<span>View object lifecycle</span>
</MenuItem>
{config?.semantic_search?.enabled && isContextMenu && (
<MenuItem onClick={findSimilar}>
<MenuItem
aria-label="Find similar tracked objects"
onClick={findSimilar}
>
<MdImageSearch className="mr-2 size-4" />
<span>Find similar</span>
</MenuItem>
@ -124,12 +130,18 @@ export default function SearchResultActions({
searchResult.has_snapshot &&
searchResult.end_time &&
!searchResult.plus_id && (
<MenuItem onClick={() => setShowFrigatePlus(true)}>
<MenuItem
aria-label="Submit to Frigate Plus"
onClick={() => setShowFrigatePlus(true)}
>
<FrigatePlusIcon className="mr-2 size-4 cursor-pointer text-primary" />
<span>Submit to Frigate+</span>
</MenuItem>
)}
<MenuItem onClick={() => setDeleteDialogOpen(true)}>
<MenuItem
aria-label="Delete this tracked object"
onClick={() => setDeleteDialogOpen(true)}
>
<LuTrash2 className="mr-2 size-4" />
<span>Delete</span>
</MenuItem>

View File

@ -154,6 +154,7 @@ export function MobilePageHeader({
>
<Button
className="absolute left-0 rounded-lg"
aria-label="Go back"
size="sm"
onClick={handleClose}
>

View File

@ -167,7 +167,11 @@ export default function CameraInfoDialog({
</div>
<DialogFooter>
<Button variant="select" onClick={() => onCopyFfprobe()}>
<Button
variant="select"
aria-label="Copy"
onClick={() => onCopyFfprobe()}
>
Copy
</Button>
</DialogFooter>

View File

@ -98,7 +98,11 @@ export default function CreateUserDialog({
)}
/>
<DialogFooter className="mt-4">
<Button variant="select" disabled={isLoading}>
<Button
variant="select"
aria-label="Create user"
disabled={isLoading}
>
{isLoading && <ActivityIndicator className="mr-2 h-4 w-4" />}
Create User
</Button>

View File

@ -27,6 +27,7 @@ export default function DeleteUserDialog({
<DialogFooter>
<Button
className="flex items-center gap-1"
aria-label="Confirm delete"
variant="destructive"
size="sm"
onClick={onDelete}

View File

@ -142,6 +142,7 @@ export default function ExportDialog({
<Trigger asChild>
<Button
className="flex items-center gap-2"
aria-label="Export"
size="sm"
onClick={() => {
const now = new Date(latestTime * 1000);
@ -307,6 +308,7 @@ export function ExportContent({
</div>
<Button
className={isDesktop ? "" : "w-full"}
aria-label="Select or export"
variant="select"
size="sm"
onClick={() => {
@ -420,6 +422,7 @@ function CustomTimeSelector({
<PopoverTrigger asChild>
<Button
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
aria-label="Start time"
variant={startOpen ? "select" : "default"}
size="sm"
onClick={() => {
@ -485,6 +488,7 @@ function CustomTimeSelector({
<PopoverTrigger asChild>
<Button
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
aria-label="End time"
variant={endOpen ? "select" : "default"}
size="sm"
onClick={() => {

View File

@ -59,8 +59,17 @@ export default function GPUInfoDialog({
<ActivityIndicator />
)}
<DialogFooter>
<Button onClick={() => setShowGpuInfo(false)}>Close</Button>
<Button variant="select" onClick={() => onCopyInfo()}>
<Button
aria-label="Close GPU info"
onClick={() => setShowGpuInfo(false)}
>
Close
</Button>
<Button
aria-label="Copy GPU info"
variant="select"
onClick={() => onCopyInfo()}
>
Copy
</Button>
</DialogFooter>
@ -88,8 +97,17 @@ export default function GPUInfoDialog({
<ActivityIndicator />
)}
<DialogFooter>
<Button onClick={() => setShowGpuInfo(false)}>Close</Button>
<Button variant="select" onClick={() => onCopyInfo()}>
<Button
aria-label="Close GPU info"
onClick={() => setShowGpuInfo(false)}
>
Close
</Button>
<Button
aria-label="Copy GPU info"
variant="select"
onClick={() => onCopyInfo()}
>
Copy
</Button>
</DialogFooter>

View File

@ -23,7 +23,11 @@ export default function MobileCameraDrawer({
return (
<Drawer open={cameraDrawer} onOpenChange={setCameraDrawer}>
<DrawerTrigger asChild>
<Button className="rounded-lg capitalize" size="sm">
<Button
className="rounded-lg capitalize"
aria-label="Cameras"
size="sm"
>
<FaVideo className="text-secondary-foreground" />
</Button>
</DrawerTrigger>

View File

@ -132,6 +132,7 @@ export default function MobileReviewSettingsDrawer({
{features.includes("export") && (
<Button
className="flex w-full items-center justify-center gap-2"
aria-label="Export"
onClick={() => {
setDrawerMode("export");
setMode("select");
@ -144,6 +145,7 @@ export default function MobileReviewSettingsDrawer({
{features.includes("calendar") && (
<Button
className="flex w-full items-center justify-center gap-2"
aria-label="Calendar"
variant={filter?.after ? "select" : "default"}
onClick={() => setDrawerMode("calendar")}
>
@ -156,6 +158,7 @@ export default function MobileReviewSettingsDrawer({
{features.includes("filter") && (
<Button
className="flex w-full items-center justify-center gap-2"
aria-label="Filter"
variant={filter?.labels || filter?.zones ? "select" : "default"}
onClick={() => setDrawerMode("filter")}
>
@ -226,6 +229,7 @@ export default function MobileReviewSettingsDrawer({
<SelectSeparator />
<div className="flex items-center justify-center p-2">
<Button
aria-label="Reset"
onClick={() => {
onUpdateFilter({
...filter,
@ -306,6 +310,7 @@ export default function MobileReviewSettingsDrawer({
<DrawerTrigger asChild>
<Button
className="rounded-lg capitalize"
aria-label="Filters"
variant={
filter?.labels || filter?.after || filter?.zones
? "select"

View File

@ -22,7 +22,11 @@ export default function MobileTimelineDrawer({
return (
<Drawer open={drawer} onOpenChange={setDrawer}>
<DrawerTrigger asChild>
<Button className="rounded-lg capitalize" size="sm">
<Button
className="rounded-lg capitalize"
aria-label="Select timeline or events list"
size="sm"
>
<FaFlag className="text-secondary-foreground" />
</Button>
</DrawerTrigger>

View File

@ -28,6 +28,7 @@ export default function SaveExportOverlay({
>
<Button
className="flex items-center gap-1 text-primary"
aria-label="Cancel"
size="sm"
onClick={onCancel}
>
@ -36,6 +37,7 @@ export default function SaveExportOverlay({
</Button>
<Button
className="flex items-center gap-1"
aria-label="Preview export"
size="sm"
onClick={onPreview}
>
@ -44,6 +46,7 @@ export default function SaveExportOverlay({
</Button>
<Button
className="flex items-center gap-1"
aria-label="Save export"
variant="select"
size="sm"
onClick={onSave}

View File

@ -36,6 +36,7 @@ export default function SetPasswordDialog({
<DialogFooter>
<Button
className="flex items-center gap-1"
aria-label="Save Password"
variant="select"
size="sm"
onClick={() => {

View File

@ -207,12 +207,14 @@ export function AnnotationSettingsPane({
<div className="flex flex-row gap-2 pt-5">
<Button
className="flex flex-1"
aria-label="Apply"
onClick={form.handleSubmit(onApply)}
>
Apply
</Button>
<Button
variant="select"
aria-label="Save"
disabled={isLoading}
className="flex flex-1"
type="submit"

View File

@ -242,6 +242,7 @@ export default function ObjectLifecycle({
<div className={cn("flex items-center gap-2")}>
<Button
className="mb-2 mt-3 flex items-center gap-2.5 rounded-lg md:mt-0"
aria-label="Go back"
size="sm"
onClick={() => setPane("overview")}
>
@ -346,6 +347,7 @@ export default function ObjectLifecycle({
<Button
variant={showControls ? "select" : "default"}
className="size-7 p-1.5"
aria-label="Adjust annotation settings"
>
<LuSettings
className="size-5"

View File

@ -153,6 +153,7 @@ export default function ReviewDetailDialog({
<Tooltip>
<TooltipTrigger>
<Button
aria-label="Share this review item"
size="sm"
onClick={() =>
shareOrCopy(`${baseUrl}review?id=${review.id}`)

View File

@ -296,7 +296,7 @@ function ObjectDetailsTab({
}
if (search.sub_label) {
return Math.round((search.data?.top_score ?? 0) * 100);
return Math.round((search.data?.sub_label_score ?? 0) * 100);
} else {
return undefined;
}
@ -440,6 +440,7 @@ function ObjectDetailsTab({
/>
{config?.semantic_search.enabled && (
<Button
aria-label="Find similar tracked objects"
onClick={() => {
setSearch(undefined);
@ -466,6 +467,7 @@ function ObjectDetailsTab({
<div className="flex items-center">
<Button
className="rounded-r-none border-r-0"
aria-label="Regenerate tracked object description"
onClick={() => regenerateDescription("thumbnails")}
>
Regenerate
@ -473,19 +475,24 @@ function ObjectDetailsTab({
{search.has_snapshot && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="rounded-l-none border-l-0 px-2">
<Button
className="rounded-l-none border-l-0 px-2"
aria-label="Expand regeneration menu"
>
<FaChevronDown className="size-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
className="cursor-pointer"
aria-label="Regenerate from snapshot"
onClick={() => regenerateDescription("snapshot")}
>
Regenerate from Snapshot
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
aria-label="Regenerate from thumbnails"
onClick={() => regenerateDescription("thumbnails")}
>
Regenerate from Thumbnails
@ -495,7 +502,11 @@ function ObjectDetailsTab({
)}
</div>
)}
<Button variant="select" onClick={updateDescription}>
<Button
variant="select"
aria-label="Save"
onClick={updateDescription}
>
Save
</Button>
</div>
@ -601,6 +612,7 @@ function ObjectSnapshotTab({
<>
<Button
className="bg-success"
aria-label="Confirm this label for Frigate Plus"
onClick={() => {
setState("uploading");
onSubmitToPlus(false);
@ -610,6 +622,7 @@ function ObjectSnapshotTab({
</Button>
<Button
className="text-white"
aria-label="Do not confirm this label for Frigate Plus"
variant="destructive"
onClick={() => {
setState("uploading");

View File

@ -131,9 +131,14 @@ export function FrigatePlusDialog({
<DialogFooter className="flex flex-row justify-end gap-2">
{state == "reviewing" && (
<>
{dialog && <Button onClick={onClose}>Cancel</Button>}
{dialog && (
<Button aria-label="Cancel" onClick={onClose}>
Cancel
</Button>
)}
<Button
className="bg-success"
aria-label="Confirm this label for Frigate Plus"
onClick={() => {
setState("uploading");
onSubmitToPlus(false);
@ -143,6 +148,7 @@ export function FrigatePlusDialog({
</Button>
<Button
className="text-white"
aria-label="Do not confirm this label for Frigate Plus"
variant="destructive"
onClick={() => {
setState("uploading");

View File

@ -76,6 +76,7 @@ export default function SearchFilterDialog({
const trigger = (
<Button
className="flex items-center gap-2"
aria-label="More Filters"
size="sm"
variant={moreFiltersSelected ? "select" : "default"}
>
@ -141,6 +142,7 @@ export default function SearchFilterDialog({
<div className="flex items-center justify-evenly p-2">
<Button
variant="select"
aria-label="Apply"
onClick={() => {
if (currentFilter != filter) {
onUpdateFilter(currentFilter);
@ -152,6 +154,7 @@ export default function SearchFilterDialog({
Apply
</Button>
<Button
aria-label="Reset filters to default values"
onClick={() => {
setCurrentFilter((prevFilter) => ({
...prevFilter,
@ -256,6 +259,7 @@ function TimeRangeFilterContent({
<PopoverTrigger asChild>
<Button
className={`text-primary ${isDesktop ? "" : "text-xs"} `}
aria-label="Select Start Time"
variant={startOpen ? "select" : "default"}
size="sm"
onClick={() => {
@ -293,6 +297,7 @@ function TimeRangeFilterContent({
<PopoverTrigger asChild>
<Button
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
aria-label="Select End Time"
variant={endOpen ? "select" : "default"}
size="sm"
onClick={() => {

View File

@ -308,11 +308,16 @@ export default function MotionMaskEditPane({
/>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}>
<Button
className="flex flex-1"
aria-label="Cancel"
onClick={onCancel}
>
Cancel
</Button>
<Button
variant="select"
aria-label="Save"
disabled={isLoading}
className="flex flex-1"
type="submit"

View File

@ -335,13 +335,18 @@ export default function ObjectMaskEditPane({
</div>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}>
<Button
className="flex flex-1"
aria-label="Cancel"
onClick={onCancel}
>
Cancel
</Button>
<Button
variant="select"
disabled={isLoading}
className="flex flex-1"
aria-label="Save"
type="submit"
>
{isLoading ? (

View File

@ -74,6 +74,7 @@ export default function PolygonEditControls({
<Button
variant="default"
className="size-6 rounded-md p-1"
aria-label="Remove last point"
disabled={!polygons[activePolygonIndex].points.length}
onClick={undo}
>
@ -87,6 +88,7 @@ export default function PolygonEditControls({
<Button
variant="default"
className="size-6 rounded-md p-1"
aria-label="Clear all points"
disabled={!polygons[activePolygonIndex].points.length}
onClick={reset}
>

View File

@ -276,6 +276,7 @@ export default function PolygonItem({
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
aria-label="Edit"
onClick={() => {
setActivePolygonIndex(index);
setEditPane(polygon.type);
@ -283,10 +284,14 @@ export default function PolygonItem({
>
Edit
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleCopyCoordinates(index)}>
<DropdownMenuItem
aria-label="Copy"
onClick={() => handleCopyCoordinates(index)}
>
Copy
</DropdownMenuItem>
<DropdownMenuItem
aria-label="Delete"
disabled={isLoading}
onClick={() => setDeleteDialogOpen(true)}
>

View File

@ -44,7 +44,11 @@ export default function SearchSettings({
]);
const trigger = (
<Button className="flex items-center gap-2" size="sm">
<Button
className="flex items-center gap-2"
aria-label="Search Settings"
size="sm"
>
<FaCog className="text-secondary-foreground" />
Settings
</Button>

View File

@ -466,13 +466,18 @@ export default function ZoneEditPane({
)}
/>
<div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}>
<Button
className="flex flex-1"
aria-label="Cancel"
onClick={onCancel}
>
Cancel
</Button>
<Button
variant="select"
disabled={isLoading}
className="flex flex-1"
aria-label="Save"
type="submit"
>
{isLoading ? (

View File

@ -283,6 +283,7 @@ export function DateRangePicker({
}): JSX.Element => (
<Button
className={cn(isSelected && "pointer-events-none text-primary")}
aria-label={label}
variant="ghost"
onClick={() => {
setPreset(preset);
@ -417,6 +418,7 @@ export function DateRangePicker({
<div className="mx-auto flex w-64 items-center justify-evenly gap-2 py-2">
<Button
variant="select"
aria-label="Apply"
onClick={() => {
setIsOpen(false);
if (
@ -436,6 +438,7 @@ export function DateRangePicker({
onReset?.();
}}
variant="ghost"
aria-label="Reset"
>
Reset
</Button>

View File

@ -1,43 +1,43 @@
import * as React from "react"
import * as React from "react";
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
} from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void;
};
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
} & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() {
const context = React.useContext(CarouselContext)
const context = React.useContext(CarouselContext);
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
throw new Error("useCarousel must be used within a <Carousel />");
}
return context
return context;
}
const Carousel = React.forwardRef<
@ -54,69 +54,69 @@ const Carousel = React.forwardRef<
children,
...props
},
ref
ref,
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
plugins,
);
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return
return;
}
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
api?.scrollPrev();
}, [api]);
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
api?.scrollNext();
}, [api]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
event.preventDefault();
scrollPrev();
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
event.preventDefault();
scrollNext();
}
},
[scrollPrev, scrollNext]
)
[scrollPrev, scrollNext],
);
React.useEffect(() => {
if (!api || !setApi) {
return
return;
}
setApi(api)
}, [api, setApi])
setApi(api);
}, [api, setApi]);
React.useEffect(() => {
if (!api) {
return
return;
}
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
onSelect(api);
api.on("reInit", onSelect);
api.on("select", onSelect);
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
api?.off("select", onSelect);
};
}, [api, onSelect]);
return (
<CarouselContext.Provider
@ -143,16 +143,16 @@ const Carousel = React.forwardRef<
{children}
</div>
</CarouselContext.Provider>
)
}
)
Carousel.displayName = "Carousel"
);
},
);
Carousel.displayName = "Carousel";
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel()
const { carouselRef, orientation } = useCarousel();
return (
<div ref={carouselRef} className="overflow-hidden">
@ -161,20 +161,20 @@ const CarouselContent = React.forwardRef<
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
className,
)}
{...props}
/>
</div>
)
})
CarouselContent.displayName = "CarouselContent"
);
});
CarouselContent.displayName = "CarouselContent";
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel()
const { orientation } = useCarousel();
return (
<div
@ -184,19 +184,19 @@ const CarouselItem = React.forwardRef<
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
className,
)}
{...props}
/>
)
})
CarouselItem.displayName = "CarouselItem"
);
});
CarouselItem.displayName = "CarouselItem";
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
<Button
@ -204,12 +204,13 @@ const CarouselPrevious = React.forwardRef<
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
className,
)}
aria-label="Previous slide"
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
@ -217,15 +218,15 @@ const CarouselPrevious = React.forwardRef<
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
)
})
CarouselPrevious.displayName = "CarouselPrevious"
);
});
CarouselPrevious.displayName = "CarouselPrevious";
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel()
const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
<Button
@ -237,8 +238,9 @@ const CarouselNext = React.forwardRef<
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
className,
)}
aria-label="Next slide"
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
@ -246,9 +248,9 @@ const CarouselNext = React.forwardRef<
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
)
})
CarouselNext.displayName = "CarouselNext"
);
});
CarouselNext.displayName = "CarouselNext";
export {
type CarouselApi,
@ -257,4 +259,4 @@ export {
CarouselItem,
CarouselPrevious,
CarouselNext,
}
};

View File

@ -192,6 +192,7 @@ function ConfigEditor() {
<Button
size="sm"
className="flex items-center gap-2"
aria-label="Copy config"
onClick={() => handleCopyConfig()}
>
<LuCopy className="text-secondary-foreground" />
@ -200,6 +201,7 @@ function ConfigEditor() {
<Button
size="sm"
className="flex items-center gap-2"
aria-label="Save and restart"
onClick={() => onHandleSaveConfig("restart")}
>
<div className="relative size-5">
@ -211,6 +213,7 @@ function ConfigEditor() {
<Button
size="sm"
className="flex items-center gap-2"
aria-label="Save only without restarting"
onClick={() => onHandleSaveConfig("saveonly")}
>
<LuSave className="text-secondary-foreground" />

View File

@ -125,6 +125,7 @@ function Exports() {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
className="text-white"
aria-label="Delete Export"
variant="destructive"
onClick={() => onHandleDelete()}
>

View File

@ -339,6 +339,7 @@ function Logs() {
<div className="flex items-center gap-2">
<Button
className="flex items-center justify-between gap-2"
aria-label="Copy logs to clipboard"
size="sm"
onClick={handleCopyLogs}
>
@ -349,6 +350,7 @@ function Logs() {
</Button>
<Button
className="flex items-center justify-between gap-2"
aria-label="Download logs"
size="sm"
onClick={handleDownloadLogs}
>
@ -365,6 +367,7 @@ function Logs() {
{initialScroll && !endVisible && (
<Button
className="absolute bottom-8 left-[50%] z-20 flex -translate-x-[50%] items-center gap-1 rounded-md p-2"
aria-label="Jump to bottom of logs"
onClick={() =>
contentRef.current?.scrollTo({
top: contentRef.current?.scrollHeight,

View File

@ -252,6 +252,7 @@ function CameraSelectButton({
const trigger = (
<Button
className="flex items-center gap-2 bg-selected capitalize hover:bg-selected"
aria-label="Select a camera"
size="sm"
>
<FaVideo className="text-background dark:text-primary" />

View File

@ -737,6 +737,7 @@ function DetectionReview({
<div className="col-span-full flex items-center justify-center">
<Button
className="text-white"
aria-label="Mark these items as reviewed"
variant="select"
onClick={() => {
setSelectedReviews([]);

View File

@ -144,6 +144,7 @@ export default function LiveBirdseyeView({
{!fullscreen ? (
<Button
className={`flex items-center gap-2 rounded-lg ${isMobile ? "ml-2" : "ml-0"}`}
aria-label="Go Back"
size={isMobile ? "icon" : "sm"}
onClick={() => navigate(-1)}
>

View File

@ -352,6 +352,7 @@ export default function LiveCameraView({
>
<Button
className={`flex items-center gap-2.5 rounded-lg`}
aria-label="Go back"
size="sm"
onClick={() => navigate(-1)}
>
@ -360,6 +361,7 @@ export default function LiveCameraView({
</Button>
<Button
className="flex items-center gap-2.5 rounded-lg"
aria-label="Show historical footage"
size="sm"
onClick={() => {
navigate("review", {
@ -388,6 +390,7 @@ export default function LiveCameraView({
{fullscreen && (
<Button
className="bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-primary"
aria-label="Go back"
size="sm"
onClick={() => navigate(-1)}
>
@ -603,6 +606,7 @@ function PtzControlPanel({
{ptz?.features?.includes("pt") && (
<>
<Button
aria-label="Move PTZ camera to the left"
onMouseDown={(e) => {
e.preventDefault();
sendPtz("MOVE_LEFT");
@ -617,6 +621,7 @@ function PtzControlPanel({
<FaAngleLeft />
</Button>
<Button
aria-label="Move PTZ camera up"
onMouseDown={(e) => {
e.preventDefault();
sendPtz("MOVE_UP");
@ -631,6 +636,7 @@ function PtzControlPanel({
<FaAngleUp />
</Button>
<Button
aria-label="Move PTZ camera down"
onMouseDown={(e) => {
e.preventDefault();
sendPtz("MOVE_DOWN");
@ -645,6 +651,7 @@ function PtzControlPanel({
<FaAngleDown />
</Button>
<Button
aria-label="Move PTZ camera to the right"
onMouseDown={(e) => {
e.preventDefault();
sendPtz("MOVE_RIGHT");
@ -663,6 +670,7 @@ function PtzControlPanel({
{ptz?.features?.includes("zoom") && (
<>
<Button
aria-label="Zoom PTZ camera in"
onMouseDown={(e) => {
e.preventDefault();
sendPtz("ZOOM_IN");
@ -677,6 +685,7 @@ function PtzControlPanel({
<MdZoomIn />
</Button>
<Button
aria-label="Zoom PTZ camera out"
onMouseDown={(e) => {
e.preventDefault();
sendPtz("ZOOM_OUT");
@ -696,6 +705,7 @@ function PtzControlPanel({
<>
<Button
className={`${clickOverlay ? "text-selected" : "text-primary"}`}
aria-label="Click in the frame to center the PTZ camera"
onClick={() => setClickOverlay(!clickOverlay)}
>
<TbViewfinder />
@ -705,7 +715,7 @@ function PtzControlPanel({
{(ptz?.presets?.length ?? 0) > 0 && (
<DropdownMenu modal={!isDesktop}>
<DropdownMenuTrigger asChild>
<Button>
<Button aria-label="PTZ camera presets">
<BsThreeDotsVertical />
</Button>
</DropdownMenuTrigger>
@ -717,6 +727,7 @@ function PtzControlPanel({
return (
<DropdownMenuItem
key={preset}
aria-label={preset}
className="cursor-pointer"
onSelect={() => sendPtz(`preset_${preset}`)}
>

View File

@ -240,6 +240,7 @@ export default function LiveDashboardView({
? "bg-blue-900 bg-opacity-60 focus:bg-blue-900 focus:bg-opacity-60"
: "bg-secondary"
}`}
aria-label="Use mobile grid layout"
size="xs"
onClick={() => setMobileLayout("grid")}
>
@ -251,6 +252,7 @@ export default function LiveDashboardView({
? "bg-blue-900 bg-opacity-60 focus:bg-blue-900 focus:bg-opacity-60"
: "bg-secondary"
}`}
aria-label="Use mobile list layout"
size="xs"
onClick={() => setMobileLayout("list")}
>
@ -267,6 +269,7 @@ export default function LiveDashboardView({
? "bg-selected text-primary"
: "bg-secondary text-secondary-foreground",
)}
aria-label="Enter layout editing mode"
size="xs"
onClick={() =>
setIsEditMode((prevIsEditMode) => !prevIsEditMode)

View File

@ -380,6 +380,7 @@ export function RecordingView({
<div className={cn("flex items-center gap-2")}>
<Button
className="flex items-center gap-2.5 rounded-lg"
aria-label="Go back"
size="sm"
onClick={() => navigate(-1)}
>
@ -388,6 +389,7 @@ export function RecordingView({
</Button>
<Button
className="flex items-center gap-2.5 rounded-lg"
aria-label="Go to the main camera live view"
size="sm"
onClick={() => {
navigate(`/#${mainCamera}`);

View File

@ -95,6 +95,7 @@ export default function AuthenticationView() {
</Heading>
<Button
className="flex items-center gap-1"
aria-label="Add a new user"
variant="default"
onClick={() => {
setShowCreate(true);
@ -114,6 +115,7 @@ export default function AuthenticationView() {
<div className="flex flex-1 justify-end space-x-2">
<Button
className="flex items-center gap-1"
aria-label="Update the user's password"
variant="secondary"
onClick={() => {
setShowSetPassword(true);
@ -125,6 +127,7 @@ export default function AuthenticationView() {
</Button>
<Button
className="flex items-center gap-1"
aria-label="Delete the user"
variant="destructive"
onClick={() => {
setShowDelete(true);

View File

@ -475,6 +475,7 @@ export default function CameraSettingsView({
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
<Button
className="flex flex-1"
aria-label="Cancel"
onClick={onCancel}
type="button"
>
@ -484,6 +485,7 @@ export default function CameraSettingsView({
variant="select"
disabled={isLoading}
className="flex flex-1"
aria-label="Save"
type="submit"
>
{isLoading ? (

View File

@ -459,6 +459,7 @@ export default function MasksAndZonesView({
<Button
variant="secondary"
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
aria-label="Add a new zone"
onClick={() => {
setEditPane("zone");
handleNewPolygon("zone");
@ -527,6 +528,7 @@ export default function MasksAndZonesView({
<Button
variant="secondary"
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
aria-label="Add a new motion mask"
onClick={() => {
setEditPane("motion_mask");
handleNewPolygon("motion_mask");
@ -596,6 +598,7 @@ export default function MasksAndZonesView({
<Button
variant="secondary"
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
aria-label="Add a new object mask"
onClick={() => {
setEditPane("object_mask");
handleNewPolygon("object_mask");

View File

@ -284,13 +284,18 @@ export default function MotionTunerView({
</div>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button className="flex flex-1" onClick={onCancel}>
<Button
className="flex flex-1"
aria-label="Reset"
onClick={onCancel}
>
Reset
</Button>
<Button
variant="select"
disabled={!changedValue || isLoading}
className="flex flex-1"
aria-label="Save"
onClick={saveToConfig}
>
{isLoading ? (

View File

@ -270,6 +270,7 @@ export default function NotificationView({
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
<Button
className="flex flex-1"
aria-label="Cancel"
onClick={onCancel}
type="button"
>
@ -279,6 +280,7 @@ export default function NotificationView({
variant="select"
disabled={isLoading}
className="flex flex-1"
aria-label="Save"
type="submit"
>
{isLoading ? (
@ -298,6 +300,7 @@ export default function NotificationView({
<div className="space-y-3">
<Separator className="my-2 flex bg-secondary" />
<Button
aria-label="Register or unregister notifications for this device"
disabled={
!config?.notifications.enabled || publicKey == undefined
}

View File

@ -266,13 +266,14 @@ export default function SearchSettingsView({
<Separator className="my-2 flex bg-secondary" />
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
<Button className="flex flex-1" onClick={onCancel}>
<Button className="flex flex-1" aria-label="Reset" onClick={onCancel}>
Reset
</Button>
<Button
variant="select"
disabled={!changedValue || isLoading}
className="flex flex-1"
aria-label="Save"
onClick={saveToConfig}
>
{isLoading ? (

View File

@ -125,7 +125,12 @@ export default function UiSettingsView() {
</p>
</div>
</div>
<Button onClick={clearStoredLayouts}>Clear All Layouts</Button>
<Button
aria-label="Clear all saved layouts"
onClick={clearStoredLayouts}
>
Clear All Layouts
</Button>
</div>
<Separator className="my-2 flex bg-secondary" />

View File

@ -541,6 +541,7 @@ export default function GeneralMetrics({
{canGetGpuInfo && (
<Button
className="cursor-pointer"
aria-label="Hardware information"
size="sm"
onClick={() => setShowVainfo(true)}
>