add variants to standardize dialog footer button layouts

This commit is contained in:
Josh Hawkins
2026-05-29 15:14:46 -05:00
parent 9c566ba299
commit e5e5500045
27 changed files with 323 additions and 384 deletions
-1
View File
@@ -275,7 +275,6 @@ export function ExportCard({
<DialogFooter> <DialogFooter>
<Button <Button
aria-label={t("editExport.saveExport")} aria-label={t("editExport.saveExport")}
size="sm"
variant="select" variant="select"
disabled={(editName?.update?.length ?? 0) == 0} disabled={(editName?.update?.length ?? 0) == 0}
onClick={() => submitRename()} onClick={() => submitRename()}
@@ -15,6 +15,7 @@ import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "../ui/dialog"; } from "../ui/dialog";
@@ -973,10 +974,9 @@ export function CameraGroupEdit({
<Separator className="my-2 flex bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<div className="flex flex-row gap-2 py-5 md:pb-0"> <DialogFooter className="py-5 md:pb-0">
<Button <Button
type="button" type="button"
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
onClick={onCancel} onClick={onCancel}
> >
@@ -985,7 +985,6 @@ export function CameraGroupEdit({
<Button <Button
variant="select" variant="select"
disabled={isLoading} disabled={isLoading}
className="flex flex-1"
aria-label={t("button.save", { ns: "common" })} aria-label={t("button.save", { ns: "common" })}
type="submit" type="submit"
> >
@@ -998,7 +997,7 @@ export function CameraGroupEdit({
t("button.save", { ns: "common" }) t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div> </DialogFooter>
</form> </form>
</Form> </Form>
); );
@@ -88,7 +88,6 @@ export function SaveSearchDialog({
<Button <Button
onClick={handleSave} onClick={handleSave}
variant="select" variant="select"
className="mb-2 md:mb-0"
aria-label={t("search.saveSearch.button.save.label")} aria-label={t("search.saveSearch.button.save.label")}
> >
{t("button.save", { ns: "common" })} {t("button.save", { ns: "common" })}
@@ -213,11 +213,8 @@ export default function CreateRoleDialog({
<FormMessage /> <FormMessage />
</div> </div>
<DialogFooter className="flex gap-2 pt-2 sm:justify-end"> <DialogFooter className="pt-2">
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
disabled={isLoading} disabled={isLoading}
onClick={handleCancel} onClick={handleCancel}
@@ -229,7 +226,6 @@ export default function CreateRoleDialog({
variant="select" variant="select"
aria-label={t("button.save", { ns: "common" })} aria-label={t("button.save", { ns: "common" })}
disabled={isLoading || !form.formState.isValid} disabled={isLoading || !form.formState.isValid}
className="flex flex-1"
type="submit" type="submit"
> >
{isLoading ? ( {isLoading ? (
@@ -241,8 +237,6 @@ export default function CreateRoleDialog({
t("button.save", { ns: "common" }) t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</form> </form>
</Form> </Form>
@@ -433,11 +433,8 @@ export default function CreateTriggerDialog({
)} )}
/> />
<DialogFooter className="flex gap-2 pt-2 sm:justify-end"> <DialogFooter className="pt-2">
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
disabled={isLoading} disabled={isLoading}
onClick={handleCancel} onClick={handleCancel}
@@ -449,7 +446,6 @@ export default function CreateTriggerDialog({
variant="select" variant="select"
aria-label={t("button.save", { ns: "common" })} aria-label={t("button.save", { ns: "common" })}
disabled={isLoading || !form.formState.isValid} disabled={isLoading || !form.formState.isValid}
className="flex flex-1"
type="submit" type="submit"
> >
{isLoading ? ( {isLoading ? (
@@ -461,8 +457,6 @@ export default function CreateTriggerDialog({
t("button.save", { ns: "common" }) t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</form> </form>
</Form> </Form>
@@ -411,11 +411,8 @@ export default function CreateUserDialog({
)} )}
/> />
<DialogFooter className="flex gap-2 pt-2 sm:justify-end"> <DialogFooter className="pt-2">
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
disabled={isLoading} disabled={isLoading}
onClick={handleCancel} onClick={handleCancel}
@@ -427,7 +424,6 @@ export default function CreateUserDialog({
variant="select" variant="select"
aria-label={t("button.save", { ns: "common" })} aria-label={t("button.save", { ns: "common" })}
disabled={isLoading || !form.formState.isValid} disabled={isLoading || !form.formState.isValid}
className="flex flex-1"
type="submit" type="submit"
> >
{isLoading ? ( {isLoading ? (
@@ -439,8 +435,6 @@ export default function CreateUserDialog({
t("button.save", { ns: "common" }) t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</form> </form>
</Form> </Form>
@@ -113,19 +113,14 @@ export function DebugReplayContent({
{isDesktop && <SelectSeparator className="my-4 bg-secondary" />} {isDesktop && <SelectSeparator className="my-4 bg-secondary" />}
<DialogFooter <DialogFooter className="mt-3 sm:mt-0">
className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-2"}
>
<Button <Button
className={isDesktop ? "" : "w-full"}
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
variant="outline"
onClick={onCancel} onClick={onCancel}
> >
{t("button.cancel", { ns: "common" })} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
className={isDesktop ? "" : "w-full"}
variant="select" variant="select"
disabled={isStarting} disabled={isStarting}
onClick={() => { onClick={() => {
@@ -70,13 +70,9 @@ export default function DeleteRoleDialog({
</div> </div>
</div> </div>
<DialogFooter className="flex gap-2 pt-2 sm:justify-end"> <DialogFooter>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
variant="outline"
disabled={isLoading} disabled={isLoading}
onClick={onCancel} onClick={onCancel}
type="button" type="button"
@@ -84,7 +80,6 @@ export default function DeleteRoleDialog({
{t("button.cancel", { ns: "common" })} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
className="flex flex-1"
aria-label={t("button.delete", { ns: "common" })} aria-label={t("button.delete", { ns: "common" })}
variant="destructive" variant="destructive"
disabled={isLoading} disabled={isLoading}
@@ -100,8 +95,6 @@ export default function DeleteRoleDialog({
t("button.delete", { ns: "common" }) t("button.delete", { ns: "common" })
)} )}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
@@ -43,11 +43,8 @@ export default function DeleteTriggerDialog({
</Trans> </Trans>
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter className="flex gap-3 sm:justify-end"> <DialogFooter>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
onClick={onCancel} onClick={onCancel}
type="button" type="button"
@@ -58,7 +55,6 @@ export default function DeleteTriggerDialog({
<Button <Button
variant="destructive" variant="destructive"
aria-label={t("button.delete", { ns: "common" })} aria-label={t("button.delete", { ns: "common" })}
className="flex flex-1 text-white"
onClick={onDelete} onClick={onDelete}
disabled={isLoading} disabled={isLoading}
> >
@@ -71,8 +67,6 @@ export default function DeleteTriggerDialog({
t("button.delete", { ns: "common" }) t("button.delete", { ns: "common" })
)} )}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
@@ -46,11 +46,8 @@ export default function DeleteUserDialog({
</p> </p>
</div> </div>
<DialogFooter className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"> <DialogFooter>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
onClick={onCancel} onClick={onCancel}
type="button" type="button"
@@ -60,13 +57,10 @@ export default function DeleteUserDialog({
<Button <Button
variant="destructive" variant="destructive"
aria-label={t("button.delete", { ns: "common" })} aria-label={t("button.delete", { ns: "common" })}
className="flex flex-1 text-white"
onClick={onDelete} onClick={onDelete}
> >
{t("button.delete", { ns: "common" })} {t("button.delete", { ns: "common" })}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
@@ -105,13 +105,15 @@ export default function EditRoleCamerasDialog({
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-5 pt-4" className="space-y-5 pt-2"
> >
<div className="space-y-2"> <div className="space-y-3">
<div>
<FormLabel>{t("roles.dialog.form.cameras.title")}</FormLabel> <FormLabel>{t("roles.dialog.form.cameras.title")}</FormLabel>
<FormDescription className="text-xs text-muted-foreground"> <FormDescription className="text-xs text-muted-foreground">
{t("roles.dialog.form.cameras.desc")} {t("roles.dialog.form.cameras.desc")}
</FormDescription> </FormDescription>
</div>
<div className="scrollbar-container max-h-[40dvh] space-y-2 overflow-y-auto"> <div className="scrollbar-container max-h-[40dvh] space-y-2 overflow-y-auto">
{cameras.map((camera) => ( {cameras.map((camera) => (
<FormField <FormField
@@ -159,11 +161,8 @@ export default function EditRoleCamerasDialog({
<FormMessage /> <FormMessage />
</div> </div>
<DialogFooter className="flex gap-2 pt-2 sm:justify-end"> <DialogFooter>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
disabled={isLoading} disabled={isLoading}
onClick={handleCancel} onClick={handleCancel}
@@ -175,7 +174,6 @@ export default function EditRoleCamerasDialog({
variant="select" variant="select"
aria-label={t("button.save", { ns: "common" })} aria-label={t("button.save", { ns: "common" })}
disabled={isLoading || !form.formState.isValid} disabled={isLoading || !form.formState.isValid}
className="flex flex-1"
type="submit" type="submit"
> >
{isLoading ? ( {isLoading ? (
@@ -187,8 +185,6 @@ export default function EditRoleCamerasDialog({
t("button.save", { ns: "common" }) t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</form> </form>
</Form> </Form>
+1 -8
View File
@@ -1049,20 +1049,15 @@ export function ExportContent({
</Tabs> </Tabs>
{isDesktop && <SelectSeparator className="my-4 bg-secondary" />} {isDesktop && <SelectSeparator className="my-4 bg-secondary" />}
<DialogFooter <DialogFooter className="mt-3 sm:mt-0">
className={isDesktop ? "" : "mt-3 flex flex-col-reverse gap-2"}
>
<Button <Button
className={isDesktop ? "" : "w-full"}
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
variant="outline"
onClick={onCancel} onClick={onCancel}
> >
{t("button.cancel", { ns: "common" })} {t("button.cancel", { ns: "common" })}
</Button> </Button>
{activeTab === "export" ? ( {activeTab === "export" ? (
<Button <Button
className={isDesktop ? "" : "w-full"}
aria-label={t("export.selectOrExport")} aria-label={t("export.selectOrExport")}
variant="select" variant="select"
disabled={isStartingExport} disabled={isStartingExport}
@@ -1086,12 +1081,10 @@ export function ExportContent({
</Button> </Button>
) : ( ) : (
<Button <Button
className={isDesktop ? "" : "w-full"}
aria-label={t("export.multiCamera.exportButton", { aria-label={t("export.multiCamera.exportButton", {
count: selectedCameraCount, count: selectedCameraCount,
})} })}
variant="select" variant="select"
size="sm"
disabled={!canStartBatchExport} disabled={!canStartBatchExport}
onClick={() => void startBatchExport()} onClick={() => void startBatchExport()}
> >
@@ -344,11 +344,7 @@ export default function MultiExportDialog({
const footer = ( const footer = (
<> <>
<Button <Button onClick={() => handleOpenChange(false)} disabled={isExporting}>
variant="outline"
onClick={() => handleOpenChange(false)}
disabled={isExporting}
>
{t("button.cancel", { ns: "common" })} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
@@ -380,7 +376,7 @@ export default function MultiExportDialog({
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{body} {body}
<DialogFooter className="gap-2">{footer}</DialogFooter> <DialogFooter>{footer}</DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );
@@ -399,7 +395,7 @@ export default function MultiExportDialog({
</DrawerDescription> </DrawerDescription>
</DrawerHeader> </DrawerHeader>
{body} {body}
<div className="mt-4 flex flex-col-reverse gap-2">{footer}</div> <DialogFooter className="mt-4">{footer}</DialogFooter>
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
); );
@@ -117,13 +117,9 @@ export default function RoleChangeDialog({
</Select> </Select>
</div> </div>
<DialogFooter className="flex gap-3 sm:justify-end"> <DialogFooter>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
variant="outline"
onClick={onCancel} onClick={onCancel}
type="button" type="button"
> >
@@ -132,15 +128,12 @@ export default function RoleChangeDialog({
<Button <Button
variant="select" variant="select"
aria-label={t("button.save", { ns: "common" })} aria-label={t("button.save", { ns: "common" })}
className="flex flex-1"
onClick={() => onSave(selectedRole)} onClick={() => onSave(selectedRole)}
type="button" type="button"
disabled={selectedRole === currentRole} disabled={selectedRole === currentRole}
> >
{t("button.save", { ns: "common" })} {t("button.save", { ns: "common" })}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
@@ -450,11 +450,8 @@ export default function SetPasswordDialog({
</div> </div>
)} )}
<DialogFooter className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"> <DialogFooter>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
onClick={onCancel} onClick={onCancel}
type="button" type="button"
@@ -465,7 +462,6 @@ export default function SetPasswordDialog({
<Button <Button
variant="select" variant="select"
aria-label={t("button.save", { ns: "common" })} aria-label={t("button.save", { ns: "common" })}
className="flex flex-1"
type="submit" type="submit"
disabled={isLoading || !form.formState.isValid} disabled={isLoading || !form.formState.isValid}
> >
@@ -478,8 +474,6 @@ export default function SetPasswordDialog({
t("button.save", { ns: "common" }) t("button.save", { ns: "common" })
)} )}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</form> </form>
</Form> </Form>
@@ -196,21 +196,16 @@ export function ShareTimestampContent({
{isDesktop && <Separator className="my-4 bg-secondary" />} {isDesktop && <Separator className="my-4 bg-secondary" />}
<DialogFooter <DialogFooter className="mt-3 sm:mt-0">
className={cn("mt-4", !isDesktop && "flex flex-col-reverse gap-2")}
>
{onCancel && ( {onCancel && (
<Button <Button
className={cn(!isDesktop && "w-full")}
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
variant="outline"
onClick={onCancel} onClick={onCancel}
> >
{t("button.cancel", { ns: "common" })} {t("button.cancel", { ns: "common" })}
</Button> </Button>
)} )}
<Button <Button
className={cn(!isDesktop && "w-full")}
variant="select" variant="select"
onClick={() => onShareTimestamp(Math.floor(selectedTimestamp))} onClick={() => onShareTimestamp(Math.floor(selectedTimestamp))}
> >
@@ -121,11 +121,8 @@ export default function DeleteCameraDialog({
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<DialogFooter className="flex gap-3 sm:justify-end"> <DialogFooter>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.cancel", { ns: "common" })} aria-label={t("button.cancel", { ns: "common" })}
onClick={handleClose} onClick={handleClose}
type="button" type="button"
@@ -135,14 +132,11 @@ export default function DeleteCameraDialog({
<Button <Button
variant="destructive" variant="destructive"
aria-label={t("button.delete", { ns: "common" })} aria-label={t("button.delete", { ns: "common" })}
className="flex flex-1 text-white"
onClick={handleDelete} onClick={handleDelete}
disabled={!selectedCamera} disabled={!selectedCamera}
> >
{t("button.delete", { ns: "common" })} {t("button.delete", { ns: "common" })}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</> </>
) : ( ) : (
@@ -173,11 +167,8 @@ export default function DeleteCameraDialog({
{t("cameraManagement.deleteCameraDialog.deleteExports")} {t("cameraManagement.deleteCameraDialog.deleteExports")}
</Label> </Label>
</div> </div>
<DialogFooter className="flex gap-3 sm:justify-end"> <DialogFooter>
<div className="flex flex-1 flex-col justify-end">
<div className="flex flex-row gap-2 pt-5">
<Button <Button
className="flex flex-1"
aria-label={t("button.back", { ns: "common" })} aria-label={t("button.back", { ns: "common" })}
onClick={handleBack} onClick={handleBack}
type="button" type="button"
@@ -187,7 +178,6 @@ export default function DeleteCameraDialog({
</Button> </Button>
<Button <Button
variant="destructive" variant="destructive"
className="flex flex-1 text-white"
onClick={handleConfirmDelete} onClick={handleConfirmDelete}
disabled={isDeleting} disabled={isDeleting}
> >
@@ -195,17 +185,13 @@ export default function DeleteCameraDialog({
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ActivityIndicator /> <ActivityIndicator />
<span> <span>
{t( {t("cameraManagement.deleteCameraDialog.confirmButton")}
"cameraManagement.deleteCameraDialog.confirmButton",
)}
</span> </span>
</div> </div>
) : ( ) : (
t("cameraManagement.deleteCameraDialog.confirmButton") t("cameraManagement.deleteCameraDialog.confirmButton")
)} )}
</Button> </Button>
</div>
</div>
</DialogFooter> </DialogFooter>
</> </>
)} )}
@@ -7,9 +7,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import { useState } from "react"; import { useState } from "react";
import { isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import FilterSwitch from "@/components/filter/FilterSwitch"; import FilterSwitch from "@/components/filter/FilterSwitch";
@@ -77,7 +75,7 @@ export default function MultiSelectDialog({
/> />
))} ))}
</div> </div>
<DialogFooter className={cn("pt-4", isMobile && "gap-2")}> <DialogFooter className="pt-4">
<Button type="button" onClick={() => setOpen(false)}> <Button type="button" onClick={() => setOpen(false)}>
{t("button.cancel")} {t("button.cancel")}
</Button> </Button>
@@ -164,10 +164,9 @@ export default function OptionAndInputDialog({
</div> </div>
)} )}
<DialogFooter className={cn("pt-2", isMobile && "gap-2")}> <DialogFooter>
<Button <Button
type="button" type="button"
variant="outline"
disabled={isLoading} disabled={isLoading}
onClick={() => { onClick={() => {
setOpen(false); setOpen(false);
@@ -9,8 +9,6 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import { isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
type TextEntryDialogProps = { type TextEntryDialogProps = {
@@ -63,7 +61,7 @@ export default function TextEntryDialog({
forbiddenPattern={forbiddenPattern} forbiddenPattern={forbiddenPattern}
forbiddenErrorMessage={forbiddenErrorMessage} forbiddenErrorMessage={forbiddenErrorMessage}
> >
<DialogFooter className={cn("pt-4", isMobile && "gap-2")}> <DialogFooter>
<Button <Button
type="button" type="button"
disabled={isSaving} disabled={isSaving}
@@ -977,7 +977,7 @@ export default function CloneCameraDialog({
)} )}
</div> </div>
<DialogFooter className="flex flex-col items-stretch gap-3 sm:flex-row sm:items-center sm:justify-between sm:space-x-0"> <DialogFooter variant="split">
<div className="flex flex-col gap-1 text-sm text-muted-foreground"> <div className="flex flex-col gap-1 text-sm text-muted-foreground">
{changeCount > 0 && ( {changeCount > 0 && (
<> <>
@@ -1005,7 +1005,7 @@ export default function CloneCameraDialog({
</> </>
)} )}
</div> </div>
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row"> <div className="flex w-full flex-col-reverse gap-2 sm:w-auto sm:flex-row">
<Button <Button
type="button" type="button"
disabled={isSubmitting} disabled={isSubmitting}
+27 -5
View File
@@ -1,6 +1,8 @@
import * as React from "react"; import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
@@ -59,15 +61,35 @@ const AlertDialogHeader = ({
); );
AlertDialogHeader.displayName = "AlertDialogHeader"; AlertDialogHeader.displayName = "AlertDialogHeader";
const alertDialogFooterVariants = cva(
"flex flex-col-reverse gap-2 sm:flex-row",
{
variants: {
variant: {
// 1-2 action buttons: full-width stacked on mobile, right-aligned auto on desktop.
// [&>button] only targets real button children, so non-button siblings are untouched.
actions: "sm:justify-end [&>button]:w-full sm:[&>button]:w-auto",
// context content (text/popover) alongside actions: space-between on desktop.
// flex-col (not -reverse) keeps the context above the buttons when stacked on mobile.
split: "flex-col sm:items-center sm:justify-between",
// alignment only; never touches children. Escape hatch for unusual content.
plain: "sm:justify-end",
},
},
defaultVariants: {
variant: "actions",
},
},
);
const AlertDialogFooter = ({ const AlertDialogFooter = ({
className, className,
variant,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement> &
VariantProps<typeof alertDialogFooterVariants>) => (
<div <div
className={cn( className={cn(alertDialogFooterVariants({ variant }), className)}
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props} {...props}
/> />
); );
+23 -5
View File
@@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog"; import * as DialogPrimitive from "@radix-ui/react-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useHistoryBack } from "@/hooks/use-history-back"; import { useHistoryBack } from "@/hooks/use-history-back";
@@ -107,15 +108,32 @@ const DialogHeader = ({
); );
DialogHeader.displayName = "DialogHeader"; DialogHeader.displayName = "DialogHeader";
const dialogFooterVariants = cva("flex flex-col-reverse gap-2 sm:flex-row", {
variants: {
variant: {
// 1-2 action buttons: full-width stacked on mobile, right-aligned auto on desktop.
// [&>button] only targets real button children, so non-button siblings are untouched.
actions: "sm:justify-end [&>button]:w-full sm:[&>button]:w-auto",
// context content (text/popover) alongside actions: space-between on desktop.
// flex-col (not -reverse) keeps the context above the buttons when stacked on mobile.
split: "flex-col sm:items-center sm:justify-between",
// alignment only; never touches children. Escape hatch for unusual content.
plain: "sm:justify-end",
},
},
defaultVariants: {
variant: "actions",
},
});
const DialogFooter = ({ const DialogFooter = ({
className, className,
variant,
...props ...props
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement> &
VariantProps<typeof dialogFooterVariants>) => (
<div <div
className={cn( className={cn(dialogFooterVariants({ variant }), className)}
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props} {...props}
/> />
); );
+5 -6
View File
@@ -1277,8 +1277,8 @@ function CaseEditorDialog({
value={description} value={description}
onChange={(event) => setDescription(event.target.value)} onChange={(event) => setDescription(event.target.value)}
/> />
<div className="flex justify-end gap-2"> <DialogFooter>
<Button variant="outline" onClick={onClose}> <Button onClick={onClose}>
{t("button.cancel", { ns: "common" })} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
@@ -1295,7 +1295,7 @@ function CaseEditorDialog({
? t("button.save", { ns: "common" }) ? t("button.save", { ns: "common" })
: t("toolbar.newCase")} : t("toolbar.newCase")}
</Button> </Button>
</div> </DialogFooter>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
@@ -1427,13 +1427,12 @@ function CaseAddExportDialog({
)} )}
</div> </div>
</div> </div>
<DialogFooter className="flex-row justify-end gap-2"> <DialogFooter>
<Button variant="outline" size="sm" onClick={onClose}> <Button onClick={onClose}>
{t("button.cancel", { ns: "common" })} {t("button.cancel", { ns: "common" })}
</Button> </Button>
<Button <Button
variant="select" variant="select"
size="sm"
disabled={selectedIds.length === 0 || isAdding} disabled={selectedIds.length === 0 || isAdding}
onClick={() => void handleAdd()} onClick={() => void handleAdd()}
> >
+1 -2
View File
@@ -1389,9 +1389,8 @@ function MotionReview({
selectedCells={pendingFilterCells} selectedCells={pendingFilterCells}
onCellsChange={setPendingFilterCells} onCellsChange={setPendingFilterCells}
/> />
<DialogFooter className="justify-end gap-1"> <DialogFooter>
<Button <Button
variant="outline"
disabled={pendingFilterCells.size === 0} disabled={pendingFilterCells.size === 0}
onClick={() => { onClick={() => {
setPendingFilterCells(new Set()); setPendingFilterCells(new Set());
@@ -547,7 +547,7 @@ function RenameStreamDialog({
<p className="text-xs text-destructive">{nameError}</p> <p className="text-xs text-destructive">{nameError}</p>
)} )}
</div> </div>
<DialogFooter className="gap-2 sm:justify-end md:gap-0"> <DialogFooter>
<DialogClose asChild> <DialogClose asChild>
<Button>{t("button.cancel", { ns: "common" })}</Button> <Button>{t("button.cancel", { ns: "common" })}</Button>
</DialogClose> </DialogClose>
@@ -628,7 +628,7 @@ function AddStreamDialog({
<p className="text-xs text-destructive">{nameError}</p> <p className="text-xs text-destructive">{nameError}</p>
)} )}
</div> </div>
<DialogFooter className="gap-2 sm:justify-end md:gap-0"> <DialogFooter>
<DialogClose asChild> <DialogClose asChild>
<Button>{t("button.cancel", { ns: "common" })}</Button> <Button>{t("button.cancel", { ns: "common" })}</Button>
</DialogClose> </DialogClose>
+1 -3
View File
@@ -654,10 +654,9 @@ export default function ProfilesView({
ns: "views/settings", ns: "views/settings",
})} })}
/> />
<DialogFooter className="gap-2 md:gap-0"> <DialogFooter>
<Button <Button
type="button" type="button"
variant="outline"
onClick={() => setAddDialogOpen(false)} onClick={() => setAddDialogOpen(false)}
disabled={addingProfile} disabled={addingProfile}
> >
@@ -746,7 +745,6 @@ export default function ProfilesView({
/> />
<DialogFooter> <DialogFooter>
<Button <Button
variant="outline"
onClick={() => setRenameProfile(null)} onClick={() => setRenameProfile(null)}
disabled={renaming} disabled={renaming}
> >