Skip to main content

The Inline Details Panel

Click the ℹ️ on any camera tile and a full-width panel slides open directly below that tile — live image, management info, location, recent activity — without covering anything and without throwing the grid into chaos. Given what you know from The Grid That Doesn't Lie, you can probably guess the trick: if every tile's row is just a number we computed, inserting a panel is arithmetic.

Insertion by renumbering

Row 1: [Cam A] [Cam B] [Cam C] [Cam D]
Row 2: [========== DETAILS PANEL ==========] ← gridColumn: 1 / -1
Row 3: [Cam E] [Cam F] [Cam G] [Cam H] ← everyone here: row += 1

CameraListComponent finds the open tile's gridRow, increments the row of every tile below it, and renders the panel div at detailPanelRow + 1 spanning 1 / -1. No DOM reordering, no layout thrash — three assignments and the browser does the rest.

Only one panel can be open; clicking a different tile's ℹ️ closes the current one first, and clicking the same one toggles it shut.

What opening actually does

The click does more than local state — it drives the URL and the data layer:

  1. setInlineDetailsCameraId(cameraId) — the grid learns where to insert
  2. The URL gains ?primaryCamera=<id> — the panel is deep-linkable
  3. TileViewPage sees the param, fetches the camera's data, joins its SignalR group
  4. cameraSelected store updates, and the panel content renders from it

So the panel isn't a popover with private state — it's the same "selected camera" machinery Location View uses, embedded in the grid.

Animations, including the exit

Opening animates via a data attribute (data-details-panel='true'detailsPanelSlideIn 0.4s, animating opacity and max-height). Closing has the classic React problem — you can't animate a component that's already unmounted — solved the classic way: a closingPanelId state keeps the panel mounted for 300 ms with data-details-panel='closing' while detailsPanelSlideOut runs, then it unmounts. Note that the row-shift logic uses inlineDetailsCameraId || closingPanelId, so the grid holds the gap open until the exit animation finishes.

Wrapping up

The panel is three ideas stacked: row-renumbering for insertion, the shared selected-camera machinery for content, and a keep-mounted-while-closing state for the exit animation. All of it falls out of the grid computing its own positions.

Next: what tiles show when the camera has been dark for a week.