mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
The Data, Authoring, Weather, Recent Imagery, Context, CCTV, Street Level and Radio panels each wrote the same frame by hand. They are now one kit component, UiPanel, with a title, badge/actions/default slots and v-model:collapsed, drawing Core's panel markup and classes beside the kit's; props cover the differences (sideways, glow, scroll, railScroller, tag, toggleWords). RailPanel binds it to the panel chrome store: v-panel-state writes its state classes at once, and its button's clicks reach PanelChrome. UiPanelToggle is the collapse button UiPanel and PanelCollapseButton share. UiPanel's collapsed drives the button and ui-panel--collapsed only; the owner hides the body by writing the root's collapsed class, since the rails measure a panel in the task that opens it. PanelCollapseButton takes startCollapsed instead of initial and initialTitle, so a button shows its state's glyph and words before startup presents the panel. Titles render through t() instead of data-i18n, and the unused "Expand panel" string is pruned. v-panel-state redraws the presented state after a re-render, in case a root's class binding ever changes.
160 lines
10 KiB
Markdown
160 lines
10 KiB
Markdown
# Panel surface contract
|
|
|
|
`src/ui/styles/panel-surfaces.css` supplies an opt-in visual surface for new
|
|
panels. It uses the application's default color, font, border and radius tokens;
|
|
Cyber supplies its existing red/slate palette, monospace typography and shaped
|
|
frame. This is CSS shared by the existing panel system, not a panel registry or
|
|
a controller. A panel ID does not need a new theme selector.
|
|
|
|
## Markup
|
|
|
|
A rail panel uses an outer owner with its existing stable `id`, matching
|
|
`data-panel-id`, and `panel-collapsible` class. Its first non-glow child is the
|
|
surface. Keep the header and any outward popup outside the scrolling body:
|
|
|
|
```vue
|
|
<section
|
|
id="example-panel"
|
|
data-panel-id="example-panel"
|
|
class="panel-collapsible"
|
|
>
|
|
<div data-panel-surface>
|
|
<header data-panel-header>
|
|
<h2 class="panel-title panel-surface-title">Example</h2>
|
|
<PanelCollapseButton
|
|
class="panel-surface-control"
|
|
target="example-panel"
|
|
:name="t('Example')"
|
|
/>
|
|
</header>
|
|
<div data-panel-body data-rail-scroller>
|
|
<label>Filter <input class="panel-surface-control" type="text" /></label>
|
|
<button class="panel-surface-control" type="button">Apply</button>
|
|
<p class="panel-surface-error" role="status" hidden></p>
|
|
</div>
|
|
<aside data-panel-popup hidden aria-label="Example help">
|
|
<!-- The panel owns this popup's placement and interactions. -->
|
|
</aside>
|
|
</div>
|
|
</section>
|
|
```
|
|
|
|
Use native controls and accessible names. `.panel-surface-control` is an
|
|
optional skin for buttons, text fields, selects and textareas; it provides
|
|
hover, focus, disabled and invalid-state styling. Native `disabled` owns actual
|
|
disablement; `aria-disabled` alone only describes a state and must be enforced
|
|
by the controller. `.panel-surface-error` supplies error color, not announcement
|
|
or validation behavior. Keep checkbox/radio semantics and labels intact.
|
|
|
|
`data-panel-header` stays outside `data-panel-body`, so it remains reachable
|
|
when content scrolls. Give the body a bounded height through its containing
|
|
surface. The rail's allocation supplies that bound; a dock or dialog owner must
|
|
supply its own `max-height` or height. Avoid a flex body whose contents shrink to
|
|
fit when those contents should instead scroll.
|
|
|
|
## Placement and ownership
|
|
|
|
| Mount | Shared contract | Owner responsibilities |
|
|
| ------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Direct child of `#left-panel-stack` | An owner with a direct `[data-panel-surface]` child receives relative positioning and allocated height; inner surface/body skin | Add the wrapper shown above, use the existing layout/disclosure owners, and retain left-rail focus/auto-collapse rules. |
|
|
| Direct child of `#right-context-rail` | The same hooks with right-rail allocation and intrinsic measurement | Keep `data-rail-scroller` on the body so measurement preserves its scroll position. Respect Cyber's one-expanded-panel policy. |
|
|
| Surface inside `#command-dock` | Surface, controls, popup decoration and inherited dock concealment | Supply geometry, stacking, disclosure, pin behavior if wanted, safe viewport bounds and dismissal. This CSS does not extend the existing Location/Visual Presets tray stack manager to arbitrary panels. |
|
|
| `dialog[data-panel-surface]` | The same surface/body skin; a closed dialog remains hidden | Set width/max-height, use `showModal()` or `show()` and `close()`, and own labels, initial/return focus and dismissal. Do not put `panel-collapsible` on a dialog. |
|
|
|
|
For rails, `--panel-expanded-width` and `--panel-collapsed-width` customize
|
|
the desired widths within the rail; otherwise Cyber uses its existing left,
|
|
right and collapsed panel widths. `--left-panel-allocated-height` and
|
|
`--right-panel-allocated-height` belong
|
|
to the existing layout owner, not to individual panels. The surface must be
|
|
the wrapper's first non-glow child because left-rail intrinsic measurement
|
|
inspects that child. Do not put an absolutely positioned popup before the
|
|
surface or inside its body. Position a popup beside or above its surface using
|
|
component CSS; the shared rules deliberately do not choose a direction that
|
|
could run beyond the viewport.
|
|
|
|
Rail measurement still considers visible child extents. A popup extending
|
|
below the surface can therefore increase its measured height. Prefer a popup
|
|
beside or above the surface, or integrate an external overlay host with the
|
|
feature's layout owner; this CSS does not change measurement algorithms.
|
|
|
|
At viewport widths of 720px or less, both rails are intentional scroll
|
|
containers. Their overflow can clip outward popups even when the panel's own
|
|
surface is unclipped. Use a popup contained within the rail's visible bounds,
|
|
or an explicitly owned overlay host outside the rail; the surface contract
|
|
does not provide automatic popup escape from these small-screen scroll regions.
|
|
|
|
For dock disclosure, use a semantic button with `.panel-surface-control` and
|
|
the feature's own accessible name, `aria-expanded` state and event binding.
|
|
Do not copy the rail example's `.panel-collapse-btn` class into the dock:
|
|
existing dock rules intentionally hide that class. The generic surface skin
|
|
does not replace the dock's disclosure policy.
|
|
|
|
A panel with Core's standard header (glow, title, badges, divider, actions
|
|
and collapse button) is a `RailPanel` (`src/ui/rail/`): the kit's `UiPanel`
|
|
bound to the `gev-panel-chrome` store, with its state classes drawn by
|
|
`v-panel-state` and its button's click asking `PanelChrome` to toggle it.
|
|
A panel with its own header uses `PanelCollapseButton`, which renders the
|
|
collapse button (`.panel-collapse-btn`) from the open state `PanelChrome`
|
|
presents to that store; until then, both show their `startCollapsed` state,
|
|
which should match the markup's `collapsed` class.
|
|
Either way, the click reaches `PanelChrome`'s `setPanelCollapsed`. `PanelChrome` collects the buttons'
|
|
targets at initialization and binds each panel's Escape. For additions
|
|
present at startup, keep those hooks and the existing chrome lifecycle. A
|
|
dynamically mounted panel must own its disclosure: its own state, button and
|
|
Escape, bound and disposed with `bindPanelDisclosure` /
|
|
`collapsePanelOnEscape` from `panelDisclosure.js` (which bind Escape only). Do not rerun the whole application chrome
|
|
initializer just to add one panel. `layoutLeftPanelRail` and
|
|
`layoutRightPanelRail` accept arbitrary direct `data-panel-id` children; their
|
|
host owns layout scheduling, observations and collapse callbacks. A rail
|
|
never writes a panel's classes: it calls `onCollapse(panel, {collapsed,
|
|
auto})`, and the host draws that state (the application presents it to the
|
|
panel chrome store, which each root's `v-panel-state` draws at once, so the
|
|
rail can measure the panel in the same task). Dynamic
|
|
content that changes size may require an owner-scheduled layout pass or an
|
|
owned `ResizeObserver`; adding CSS attributes does not register new observers.
|
|
|
|
Persisted/share-link state, pinning, provider loading, keyboard shortcuts and
|
|
Cockpit-specific mounting still require deliberate integration with their
|
|
current owners. This contract does not add entries to named persistence, dock,
|
|
Cockpit or share-state policies. Dispose listeners, observers and timers,
|
|
dismiss open popups/dialogs and restore focus before removing a dynamic panel.
|
|
|
|
## Decoration, popups and visibility
|
|
|
|
The shared surface reserves `::before` for its non-interactive background. The
|
|
Cyber frame clips this pseudo-element only; the surface stays unclipped with
|
|
visible overflow. This follows the existing Display and Global Context pattern.
|
|
Putting the shape on an interactive ancestor clips all descendant popups even
|
|
if their `z-index` is high. A scrolling body also clips descendants by design,
|
|
so outward popups must be siblings of the body, or use an explicitly owned
|
|
overlay host outside every clipping ancestor.
|
|
|
|
`data-panel-popup` gives a positioned popup the same decorative and control
|
|
tokens. It is not a popover API, focus trap, collision solver or top-layer
|
|
manager. The owner must keep it inside the viewport, above relevant siblings,
|
|
and reachable by pointer and keyboard. A native modal dialog participates in
|
|
the browser top layer; a surface in a rail does not. Transform, containment,
|
|
mask, clipping or stacking rules on an additional ancestor can still obstruct
|
|
either an ordinary panel or its popup. Arbitrary future markup is not covered.
|
|
|
|
`hidden`, a closed native dialog and collapsed bodies/popups remain hidden.
|
|
Clean UI and recording conceal conforming surfaces, including outward popups;
|
|
owners must close modal dialogs when entering these modes so an invisible
|
|
modal cannot leave the application inert. Existing Cockpit rail and dock
|
|
exclusions remain in force. New left-rail/Cockpit functionality needs an
|
|
explicit product integration; the surface attribute grants no mode exception.
|
|
Never use theme styling to alter stored preferences or force hidden content
|
|
visible.
|
|
|
|
## Check an addition
|
|
|
|
Use the real application with the default and Cyber themes. Check open,
|
|
collapsed, hidden, disabled/error, theme round trips, clean UI/recording and
|
|
the intended Cockpit behavior. Exercise keyboard focus, Escape, pointer input,
|
|
the last scrolling item, resize without reload and any pin/unpin controls.
|
|
For an outward popup, test visible pixels and pointer hit-testing outside the
|
|
surface; a positive bounding box alone does not prove it escaped clipping.
|
|
Check each mount the addition actually supports. The CSS contract cannot
|
|
establish provider correctness, disclosure lifecycle or responsive placement
|
|
without those feature-specific checks.
|