feat(security): reflow the node Security page for mobile (#1372)

* feat(security): reflow the node Security page for mobile

Below the md breakpoint the Security page now reads as a phone surface
instead of a squeezed desktop, with no change to the desktop layout.

- Masthead stat cluster moves into a full-width 3-cell strip
  (critical / high / last scan) below the tab strip, since the masthead
  hides its inline cluster on a phone.
- The eight-section tab strip becomes a horizontally scrollable mono row
  with an edge mask-fade and a cyan underline on the active tab; every
  section stays reachable by scroll.
- The six totals render as a 3x2 hairline-divided grid instead of the
  640px-wide rail that forced a horizontal scroll.
- The Images tab becomes a filterable, scrollable list (severity dot,
  truncated ref, freshness, critical/high count tags) with a chip row,
  in place of the desktop table.
- A freshness footer band states scan recency and scanner version.

All mobile treatment is gated by useIsMobile() or max-md: utilities, so
the desktop view is byte-identical. Charts are reused full-width.

* feat(security): make the mobile Security page a bespoke masthead-led screen

On a phone the Security page now drops the global top bar and leads with
its masthead (the notifications + more-menu cluster moves into the
masthead's right slot), matching Home and Fleet so the mobile shell is
continuous across pages. The view is reclassified bespoke and rendered
through the masthead-led path; the desktop layout is unchanged.

The mobile "more" menu now lists every destination instead of omitting
the bottom-tab views, so the same menu opens the same set on every
screen rather than changing contents from page to page.
This commit is contained in:
Anso
2026-06-14 21:28:13 -04:00
committed by GitHub
parent a5109e7916
commit 0066887cee
12 changed files with 712 additions and 96 deletions
+4 -9
View File
@@ -5,11 +5,6 @@ import { Sheet, SheetContent, SheetTrigger } from './ui/sheet';
import { cn } from '@/lib/utils';
import type { NavItem, ActiveView } from './EditorLayout/hooks/useViewNavigationState';
// Destinations the bottom tab bar already exposes; the "more" menu omits these
// so it lists only the secondary destinations (auto-updates, app store,
// observability, etc.). Kept in sync with MobileTabBar's tab set.
const TAB_BAR_VIEWS = new Set<string>(['dashboard', 'fleet', 'scheduled-ops', 'settings']);
interface MobileMoreMenuProps {
/** The already-gated nav items (admin / remote / paid filtering applied). */
navItems: NavItem[];
@@ -22,12 +17,12 @@ interface MobileMoreMenuProps {
/**
* The "more destinations" affordance for the mobile content screens. On a phone
* the global TopBar is dropped and each screen's masthead leads, so this hosts
* every secondary destination the bottom tab bar does not (auto-updates, app
* store, observability, etc.) plus the theme and account controls.
* the full destination list plus the theme and account controls. It lists every
* gated nav item (the bottom tab bar's primaries included) so the same menu
* opens the same set on every screen, rather than changing contents per page.
*/
export function MobileMoreMenu({ navItems, activeView, onNavigate, footer }: MobileMoreMenuProps) {
const [open, setOpen] = useState(false);
const secondaryItems = navItems.filter(item => !TAB_BAR_VIEWS.has(item.value));
return (
<Sheet open={open} onOpenChange={setOpen}>
@@ -46,7 +41,7 @@ export function MobileMoreMenu({ navItems, activeView, onNavigate, footer }: Mob
<p className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-icon">Navigate</p>
</div>
<nav className="flex flex-1 flex-col gap-1 overflow-y-auto p-2">
{secondaryItems.map(({ value, label, icon: Icon }) => (
{navItems.map(({ value, label, icon: Icon }) => (
<button
key={value}
type="button"