UI tweaks (#23346)
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled

* remove redundant per-view toasters in settings

* add variants to standardize dialog footer button layouts

* remove text-md

this class name compiles to nothing in tailwind. we used to add it to prevent iOS from zooming when focusing on an input, but that is now solved via the viewport meta in index.html

* make wizard footers consistent with dialog footers

* consistent destructive button style

remove text-white from individual buttons and add it to the variant
This commit is contained in:
Josh Hawkins
2026-05-29 16:00:30 -06:00
committed by GitHub
parent 4b6fa49449
commit 6fdd65ddb5
88 changed files with 440 additions and 593 deletions
@@ -668,7 +668,6 @@ function LibrarySelector({
</Button>
<Button
variant="destructive"
className="text-white"
onClick={() => {
if (confirmDelete) {
handleDeleteCategory(confirmDelete);
+4 -9
View File
@@ -1389,9 +1389,8 @@ function MotionReview({
selectedCells={pendingFilterCells}
onCellsChange={setPendingFilterCells}
/>
<DialogFooter className="justify-end gap-1">
<DialogFooter>
<Button
variant="outline"
disabled={pendingFilterCells.size === 0}
onClick={() => {
setPendingFilterCells(new Set());
@@ -1432,9 +1431,7 @@ function MotionReview({
<div className="space-y-4 py-2">
{!isDesktop && (
<div className="space-y-1">
<div className="text-md">
{t("motionPreviews.mobileSettingsTitle")}
</div>
<div>{t("motionPreviews.mobileSettingsTitle")}</div>
<div className="text-xs text-muted-foreground">
{t("motionPreviews.mobileSettingsDesc")}
</div>
@@ -1443,9 +1440,7 @@ function MotionReview({
<div className="space-y-3">
<div className="space-y-0.5">
<div className="text-md">
{t("motionPreviews.speed")}
</div>
<div>{t("motionPreviews.speed")}</div>
<div className="text-xs text-muted-foreground">
{t("motionPreviews.speedDesc")}
</div>
@@ -1474,7 +1469,7 @@ function MotionReview({
<div className="space-y-3">
<div className="space-y-0.5">
<div className="text-md">{t("motionPreviews.dim")}</div>
<div>{t("motionPreviews.dim")}</div>
<div className="text-xs text-muted-foreground">
{t("motionPreviews.dimDesc")}
</div>
+1 -1
View File
@@ -784,7 +784,7 @@ export default function LiveCameraView({
transcription != null && (
<div
ref={transcriptionRef}
className="text-md scrollbar-container absolute bottom-4 left-1/2 max-h-[15vh] w-[75%] -translate-x-1/2 overflow-y-auto rounded-lg bg-black/70 p-2 text-white md:w-[50%]"
className="scrollbar-container absolute bottom-4 left-1/2 max-h-[15vh] w-[75%] -translate-x-1/2 overflow-y-auto rounded-lg bg-black/70 p-2 text-white md:w-[50%]"
>
{transcription}
</div>
@@ -630,7 +630,7 @@ function SearchRangeSelector({
/>
<SelectSeparator className="bg-secondary" />
<input
className="text-md mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
className="mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
id="startTime"
type="time"
value={startClock}
@@ -696,7 +696,7 @@ function SearchRangeSelector({
/>
<SelectSeparator className="bg-secondary" />
<input
className="text-md mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
className="mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
id="endTime"
type="time"
value={endClock}
@@ -1052,7 +1052,6 @@ export default function MotionSearchView({
</div>
<Button
variant="destructive"
className="text-white"
size="sm"
onClick={() => {
void cancelMotionSearchJob(jobId, jobCamera);
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import { FrigateConfig } from "@/types/frigateConfig";
import { Toaster } from "@/components/ui/sonner";
import useSWR from "swr";
import Heading from "@/components/ui/heading";
import { User } from "@/types/user";
@@ -790,7 +789,6 @@ export default function AuthenticationView({
return (
<div className="flex size-full flex-col">
<Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto pb-2 md:order-none md:mr-3 md:mt-0">
{section === "users" && UsersSection}
{section === "roles" && RolesSection}
@@ -250,7 +250,7 @@ export default function CameraManagementView({
<Button
variant="destructive"
onClick={() => setShowDeleteDialog(true)}
className="mb-2 flex max-w-48 items-center gap-2 text-white"
className="mb-2 flex max-w-48 items-center gap-2"
>
<LuTrash2 className="h-4 w-4" />
{t("cameraManagement.deleteCamera")}
@@ -261,7 +261,7 @@ export default function CameraManagementView({
{enabledCameras.length + disabledCameras.length > 0 && (
<div className="mb-5 space-y-3">
<div className="space-y-0.5">
<div className="text-md font-medium">
<div className="font-medium">
{t("cameraManagement.clone.sectionTitle")}
</div>
<p className="text-sm text-muted-foreground">
@@ -22,7 +22,6 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
import Heading from "@/components/ui/heading";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Toaster } from "@/components/ui/sonner";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
@@ -598,7 +597,6 @@ export default function DetectorsAndModelSettingsView({
return (
<div className="flex size-full flex-col md:pr-2">
<Toaster position="top-center" closeButton={true} />
<div className="mb-1 flex items-center justify-between gap-4 pt-2">
<div className="flex max-w-5xl flex-col">
<Heading as="h4">{t("detectorsAndModel.title")}</Heading>
@@ -306,9 +306,7 @@ export default function EnrichmentsSettingsView({
</div>
<div className="mt-2 flex flex-col space-y-6">
<div className="space-y-0.5">
<div className="text-md">
{t("enrichments.semanticSearch.modelSize.label")}
</div>
<div>{t("enrichments.semanticSearch.modelSize.label")}</div>
<div className="space-y-1 text-sm text-muted-foreground">
<p>
<Trans ns="views/settings">
@@ -436,9 +434,7 @@ export default function EnrichmentsSettingsView({
</div>
</div>
<div className="space-y-0.5">
<div className="text-md">
{t("enrichments.faceRecognition.modelSize.label")}
</div>
<div>{t("enrichments.faceRecognition.modelSize.label")}</div>
<div className="space-y-1 text-sm text-muted-foreground">
<p>
<Trans ns="views/settings">
@@ -4,7 +4,6 @@ import { Link, useNavigate } from "react-router-dom";
import useSWR from "swr";
import { CheckCircle2, XCircle } from "lucide-react";
import { LuExternalLink } from "react-icons/lu";
import { Toaster } from "@/components/ui/sonner";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import { Button } from "@/components/ui/button";
import Heading from "@/components/ui/heading";
@@ -35,7 +34,6 @@ export default function FrigatePlusSettingsView(_props: SettingsPageProps) {
return (
<div className="flex size-full flex-col md:pr-2">
<Toaster position="top-center" closeButton={true} />
<div className="w-full max-w-5xl space-y-6 pt-2">
<div className="flex flex-col gap-0">
<Heading as="h4" className="mb-2">
@@ -446,10 +446,7 @@ export default function Go2RtcStreamsSettingsView({
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<AlertDialogAction
className={cn(
buttonVariants({ variant: "destructive" }),
"text-white",
)}
className={cn(buttonVariants({ variant: "destructive" }))}
onClick={() => deleteDialog && deleteStream(deleteDialog)}
>
{t("go2rtcStreams.deleteStream")}
@@ -533,7 +530,6 @@ function RenameStreamDialog({
<div className="space-y-2 py-2">
<Label>{t("go2rtcStreams.newStreamName")}</Label>
<Input
className="text-md"
value={newName}
onChange={(e) => setNewName(e.target.value)}
onKeyDown={(e) => {
@@ -547,7 +543,7 @@ function RenameStreamDialog({
<p className="text-xs text-destructive">{nameError}</p>
)}
</div>
<DialogFooter className="gap-2 sm:justify-end md:gap-0">
<DialogFooter>
<DialogClose asChild>
<Button>{t("button.cancel", { ns: "common" })}</Button>
</DialogClose>
@@ -614,7 +610,6 @@ function AddStreamDialog({
<Label>{t("go2rtcStreams.streamName")}</Label>
<Input
value={name}
className="text-md"
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && canSubmit) {
@@ -628,7 +623,7 @@ function AddStreamDialog({
<p className="text-xs text-destructive">{nameError}</p>
)}
</div>
<DialogFooter className="gap-2 sm:justify-end md:gap-0">
<DialogFooter>
<DialogClose asChild>
<Button>{t("button.cancel", { ns: "common" })}</Button>
</DialogClose>
@@ -924,7 +919,7 @@ function StreamUrlEntry({
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Input
className="text-md h-8 pr-10"
className="h-8 pr-10"
value={baseUrlForDisplay}
onChange={(e) => handleBaseUrlChange(e.target.value)}
onFocus={() => setIsFocused(true)}
+3 -5
View File
@@ -15,7 +15,6 @@ import {
} from "@/components/ui/hover-card";
import copy from "copy-to-clipboard";
import { toast } from "sonner";
import { Toaster } from "@/components/ui/sonner";
import { Button } from "@/components/ui/button";
import {
Tooltip,
@@ -730,7 +729,6 @@ export default function MasksAndZonesView({
<>
{cameraConfig && editingPolygons && (
<div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mr-3 md:mt-0 md:w-3/12 md:min-w-0 md:shrink-0">
{editPane == "zone" && (
<ZoneEditPane
@@ -793,7 +791,7 @@ export default function MasksAndZonesView({
<div className="my-3 flex flex-row items-center justify-between">
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-md cursor-default">
<div className="cursor-default">
{t("masksAndZones.zones.label")}
</div>
</HoverCardTrigger>
@@ -871,7 +869,7 @@ export default function MasksAndZonesView({
<div className="my-3 flex flex-row items-center justify-between">
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-md cursor-default">
<div className="cursor-default">
{t("masksAndZones.motionMasks.label")}
</div>
</HoverCardTrigger>
@@ -953,7 +951,7 @@ export default function MasksAndZonesView({
<div className="my-3 flex flex-row items-center justify-between">
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-md cursor-default">
<div className="cursor-default">
{t("masksAndZones.objectMasks.label")}
</div>
</HoverCardTrigger>
@@ -2,7 +2,6 @@ import Heading from "@/components/ui/heading";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Toaster } from "@/components/ui/sonner";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -105,7 +104,6 @@ export default function MediaSyncSettingsView() {
return (
<>
<div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto px-2 md:order-none">
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
<div className="col-span-1">
+2 -4
View File
@@ -15,7 +15,6 @@ import {
import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Toaster } from "@/components/ui/sonner";
import { toast } from "sonner";
import { Separator } from "@/components/ui/separator";
import { Link } from "react-router-dom";
@@ -184,7 +183,6 @@ export default function MotionTunerView({
return (
<div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mr-3 md:mt-0 md:w-3/12">
<Heading as="h4" className="mb-2">
{t("motionDetectionTuner.title")}
@@ -208,7 +206,7 @@ export default function MotionTunerView({
<div className="flex w-full flex-col space-y-6">
<div className="mt-2 space-y-6">
<div className="space-y-0.5">
<Label htmlFor="motion-threshold" className="text-md">
<Label htmlFor="motion-threshold">
{t("motionDetectionTuner.Threshold.title")}
</Label>
<div className="my-2 text-sm text-muted-foreground">
@@ -237,7 +235,7 @@ export default function MotionTunerView({
</div>
<div className="mt-2 space-y-6">
<div className="space-y-0.5">
<Label htmlFor="motion-threshold" className="text-md">
<Label htmlFor="motion-threshold">
{t("motionDetectionTuner.contourArea.title")}
</Label>
<div className="my-2 text-sm text-muted-foreground">
@@ -415,7 +415,7 @@ function ObjectList({ cameraConfig, objects }: ObjectListProps) {
</div>
</div>
<div className="flex w-8/12 flex-row items-center justify-end">
<div className="text-md mr-2 w-1/3">
<div className="mr-2 w-1/3">
<div className="flex flex-col items-end justify-end">
<p className="mb-1.5 text-sm text-primary-variant">
{t("debug.objectShapeFilterDrawing.score")}
@@ -426,7 +426,7 @@ function ObjectList({ cameraConfig, objects }: ObjectListProps) {
%
</div>
</div>
<div className="text-md mr-2 w-1/3">
<div className="mr-2 w-1/3">
<div className="flex flex-col items-end justify-end">
<p className="mb-1.5 text-sm text-primary-variant">
{t("debug.objectShapeFilterDrawing.ratio")}
@@ -434,7 +434,7 @@ function ObjectList({ cameraConfig, objects }: ObjectListProps) {
{obj.ratio ? obj.ratio.toFixed(2).toString() : "-"}
</div>
</div>
<div className="text-md mr-2 w-1/3">
<div className="mr-2 w-1/3">
<div className="flex flex-col items-end justify-end">
<p className="mb-1.5 text-sm text-primary-variant">
{t("debug.objectShapeFilterDrawing.area")}
@@ -505,7 +505,7 @@ function AudioList({ cameraConfig, audioDetections }: AudioListProps) {
<div className="ml-3 text-lg">{getTranslatedLabel(key)}</div>
</div>
<div className="flex w-8/12 flex-row items-center justify-end">
<div className="text-md mr-2 w-1/3">
<div className="mr-2 w-1/3">
<div className="flex flex-col items-end justify-end">
<p className="mb-1.5 text-sm text-primary-variant">
{t("debug.audio.score")}
+3 -5
View File
@@ -24,7 +24,7 @@ import { resolveCameraName } from "@/hooks/use-camera-friendly-name";
import { useDocDomain } from "@/hooks/use-doc-domain";
import { cn } from "@/lib/utils";
import Heading from "@/components/ui/heading";
import { Button } from "@/components/ui/button";
import { Button, buttonVariants } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import NameAndIdFields from "@/components/input/NameAndIdFields";
@@ -654,10 +654,9 @@ export default function ProfilesView({
ns: "views/settings",
})}
/>
<DialogFooter className="gap-2 md:gap-0">
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setAddDialogOpen(false)}
disabled={addingProfile}
>
@@ -709,7 +708,7 @@ export default function ProfilesView({
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-white hover:bg-destructive/90"
className={cn(buttonVariants({ variant: "destructive" }))}
onClick={(e) => {
e.preventDefault();
handleDeleteProfile();
@@ -746,7 +745,6 @@ export default function ProfilesView({
/>
<DialogFooter>
<Button
variant="outline"
onClick={() => setRenameProfile(null)}
disabled={renaming}
>
@@ -10,7 +10,6 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Toaster } from "@/components/ui/sonner";
import { useCallback, useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import axios from "axios";
@@ -59,7 +58,6 @@ export default function RegionGridSettingsView({
return (
<>
<div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto px-2 md:order-none">
<Heading as="h4" className="mb-2 hidden md:block">
{t("maintenance.regionGrid.title")}
@@ -85,7 +83,7 @@ export default function RegionGridSettingsView({
onClick={() => setIsConfirmOpen(true)}
disabled={isClearing}
variant="destructive"
className="flex flex-1 text-white md:max-w-sm"
className="flex flex-1 md:max-w-sm"
>
{t("maintenance.regionGrid.clear")}
</Button>
@@ -108,10 +106,7 @@ export default function RegionGridSettingsView({
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<AlertDialogAction
className={cn(
buttonVariants({ variant: "destructive" }),
"text-white",
)}
className={cn(buttonVariants({ variant: "destructive" }))}
onClick={handleClear}
>
{t("maintenance.regionGrid.clear")}
-2
View File
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Toaster } from "@/components/ui/sonner";
import useSWR from "swr";
import axios from "axios";
import { Button } from "@/components/ui/button";
@@ -443,7 +442,6 @@ export default function TriggerView({
return (
<div className="flex size-full flex-col md:flex-row">
<Toaster position="top-center" closeButton={true} />
<div
className={cn(
"scrollbar-container order-last mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto pb-2",
+1 -2
View File
@@ -1,7 +1,7 @@
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { ReactNode, useCallback, useContext, useEffect } from "react";
import { Toaster, toast } from "sonner";
import { toast } from "sonner";
import { Button } from "../../components/ui/button";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
@@ -211,7 +211,6 @@ export default function UiSettingsView() {
return (
<div className="flex size-full flex-col">
<Toaster position="top-center" closeButton={true} />
<div className="scrollbar-container mb-2 mt-2 flex h-full w-full flex-col overflow-y-auto pb-2">
<Heading as="h4" className="mb-3">
{t("general.title")}