mobile tweaks

stack buttons, add more space on profiles pane, and move the overridden badge beneath the description
This commit is contained in:
Josh Hawkins 2026-03-20 07:56:24 -05:00
parent 705becba8c
commit e7ef6139ec
3 changed files with 74 additions and 39 deletions

View File

@ -936,7 +936,7 @@ export function ConfigSection({
</span> </span>
</div> </div>
)} )}
<div className="flex w-full items-center gap-2 md:w-auto"> <div className="flex w-full flex-col gap-2 sm:flex-row sm:items-center md:w-auto">
{((effectiveLevel === "camera" && isOverridden) || {((effectiveLevel === "camera" && isOverridden) ||
effectiveLevel === "global") && effectiveLevel === "global") &&
!hasChanges && !hasChanges &&

View File

@ -385,7 +385,7 @@ export default function ProfilesView({
{/* Active Profile + Add Profile bar */} {/* Active Profile + Add Profile bar */}
{(hasProfiles || profilesUIEnabled) && ( {(hasProfiles || profilesUIEnabled) && (
<div className="my-4 flex items-center justify-between rounded-lg border border-border/70 bg-card/30 p-4"> <div className="my-4 flex flex-col gap-3 rounded-lg border border-border/70 bg-card/30 p-4 sm:flex-row sm:items-center sm:justify-between">
{hasProfiles && ( {hasProfiles && (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-sm font-semibold text-primary-variant"> <span className="text-sm font-semibold text-primary-variant">
@ -470,12 +470,12 @@ export default function ProfilesView({
)} )}
> >
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<div className="flex cursor-pointer items-center justify-between px-4 py-3 hover:bg-secondary/30"> <div className="flex cursor-pointer flex-wrap items-center gap-y-2 px-4 py-3 hover:bg-secondary/30">
<div className="flex items-center gap-3"> <div className="flex min-w-0 items-center gap-3">
{isExpanded ? ( {isExpanded ? (
<LuChevronDown className="size-4 text-muted-foreground" /> <LuChevronDown className="size-4 shrink-0 text-muted-foreground" />
) : ( ) : (
<LuChevronRight className="size-4 text-muted-foreground" /> <LuChevronRight className="size-4 shrink-0 text-muted-foreground" />
)} )}
<span <span
className={cn( className={cn(
@ -483,13 +483,13 @@ export default function ProfilesView({
color.dot, color.dot,
)} )}
/> />
<span className="font-medium"> <span className="truncate font-medium">
{profileFriendlyNames?.get(profile) ?? profile} {profileFriendlyNames?.get(profile) ?? profile}
</span> </span>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="size-6 text-muted-foreground hover:text-primary" className="size-6 shrink-0 text-muted-foreground hover:text-primary"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setRenameProfile(profile); setRenameProfile(profile);
@ -500,6 +500,8 @@ export default function ProfilesView({
> >
<Pencil className="size-3" /> <Pencil className="size-3" />
</Button> </Button>
</div>
<div className="ml-auto flex items-center gap-3">
{isActive && ( {isActive && (
<Badge <Badge
variant="secondary" variant="secondary"
@ -508,8 +510,6 @@ export default function ProfilesView({
{t("profiles.active", { ns: "views/settings" })} {t("profiles.active", { ns: "views/settings" })}
</Badge> </Badge>
)} )}
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
{cameras.length > 0 {cameras.length > 0
? t("profiles.cameraCount", { ? t("profiles.cameraCount", {
@ -523,7 +523,7 @@ export default function ProfilesView({
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="size-7 text-muted-foreground hover:text-destructive" className="size-7 shrink-0 text-muted-foreground hover:text-destructive"
disabled={deleting && deleteProfile === profile} disabled={deleting && deleteProfile === profile}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();

View File

@ -131,34 +131,35 @@ export function SingleSectionPage({
return ( return (
<div className="flex size-full flex-col lg:pr-2"> <div className="flex size-full flex-col lg:pr-2">
<div className="mb-5 flex items-center justify-between gap-4"> <div className="mb-5 flex flex-col gap-2">
<div className="flex flex-col"> <div className="flex items-center justify-between gap-4">
<Heading as="h4"> <div className="flex flex-col">
{t(`${sectionKey}.label`, { ns: sectionNamespace })} <Heading as="h4">
</Heading> {t(`${sectionKey}.label`, { ns: sectionNamespace })}
{i18n.exists(`${sectionKey}.description`, { </Heading>
ns: sectionNamespace, {i18n.exists(`${sectionKey}.description`, {
}) && ( ns: sectionNamespace,
<div className="my-1 text-sm text-muted-foreground"> }) && (
{t(`${sectionKey}.description`, { ns: sectionNamespace })} <div className="my-1 text-sm text-muted-foreground">
</div> {t(`${sectionKey}.description`, { ns: sectionNamespace })}
)} </div>
{sectionDocsUrl && ( )}
<div className="flex items-center text-sm text-primary-variant"> {sectionDocsUrl && (
<Link <div className="flex items-center text-sm text-primary-variant">
to={sectionDocsUrl} <Link
target="_blank" to={sectionDocsUrl}
rel="noopener noreferrer" target="_blank"
className="inline" rel="noopener noreferrer"
> className="inline"
{t("readTheDocumentation", { ns: "common" })} >
<LuExternalLink className="ml-2 inline-flex size-3" /> {t("readTheDocumentation", { ns: "common" })}
</Link> <LuExternalLink className="ml-2 inline-flex size-3" />
</div> </Link>
)} </div>
</div> )}
<div className="flex flex-col items-end gap-2 md:flex-row md:items-center"> </div>
<div className="flex flex-wrap items-center justify-end gap-2"> {/* Desktop: badge inline next to title */}
<div className="hidden shrink-0 sm:flex sm:flex-wrap sm:items-center sm:gap-2">
{level === "camera" && {level === "camera" &&
showOverrideIndicator && showOverrideIndicator &&
sectionStatus.isOverridden && ( sectionStatus.isOverridden && (
@ -211,6 +212,40 @@ export function SingleSectionPage({
)} )}
</div> </div>
</div> </div>
{/* Mobile: badge below title/description */}
<div className="flex flex-wrap items-center gap-2 sm:hidden">
{level === "camera" &&
showOverrideIndicator &&
sectionStatus.isOverridden && (
<Badge
variant="secondary"
className={cn(
"cursor-default border-2 text-center text-xs text-primary-variant",
sectionStatus.overrideSource === "profile" && profileColor
? profileColor.border
: "border-selected",
)}
>
{sectionStatus.overrideSource === "profile"
? t("button.overriddenBaseConfig", {
ns: "views/settings",
defaultValue: "Overridden (Base Config)",
})
: t("button.overriddenGlobal", {
ns: "views/settings",
defaultValue: "Overridden (Global)",
})}
</Badge>
)}
{sectionStatus.hasChanges && (
<Badge
variant="secondary"
className="cursor-default bg-danger text-xs text-white hover:bg-danger"
>
{t("modified", { ns: "common", defaultValue: "Modified" })}
</Badge>
)}
</div>
</div> </div>
<ConfigSectionTemplate <ConfigSectionTemplate
sectionKey={sectionKey} sectionKey={sectionKey}