Dashboard
Grid of resizable panes, dragged by the edges they share.
c2-dashboard is a CSS grid whose tracks the user can drag. Its children are c2-dash-card panes, each placed by
col / row / col-span / row-span; a pane draws a handle on every edge it shares with a neighbour, and dragging
one resizes the whole track, so the panes on either side of it stay aligned. columns and rows take either a track
count (columns="3" is three equal columns) or an explicit list (columns="320px 1fr"). The first drag switches a
track to pixels and the fr tracks around it give up the space in proportion to their weight, down to
min-column-width / min-row-height and to whatever min-width / min-height the panes over those tracks ask for.
Give the grid a height whenever its rows use fr — the row tracks divide the host’s height.
A pane draws no surface of its own: put a c2-card, or any markup, in its default slot. Filling header,
actions or footer turns that row on; with header and actions both empty, the optional expand-width /
expand-height / expand-full controls float over the top-right corner instead. Your own buttons go in actions
(at the end of the header row) or in controls (inside the built-in group, right next to the expand buttons), and
each built-in icon is a slot of its own — expand-full-icon, collapse-full-icon and the width and height pairs —
whose fallback is the default drawing, so a pane can use the application’s icon set and keep the behaviour. An
expanded pane covers its neighbours on an opaque --c2-dash-card__expanded--background (the theme’s surface colour
by default); the pane itself stays transparent at rest. Whatever you slot into the pane fills it — the body is a
column flex box and each slotted child is a flex: 1 item — so a chart or a code editor follows the row track
without a height: 100% chain. The whole gutter between two panes drags: each handle is centred on its edge and as
thick as --c2-dashboard--gap.
Set storage-key and the track sizes are written to localStorage at the end of each gesture and restored on the
next load; layout-change reports them either way, and reset() puts the authored tracks back. A handle is a
window splitter: it is in the tab order and the arrow keys move it, 10px a step and 1px with Shift.
For a grid that changes shape with the viewport, set layouts instead of remounting: an ordered list of entries,
each with a media query and the columns, rows and layout (placements by card-id) to use while it matches.
The first match wins, the grid switches in place as the viewport crosses a breakpoint, layout-change reports the
switch, and each entry keeps its own stored sizes under <storage-key>@<media>.
grid.layouts = [
{ media: '(max-width: 1279px)', columns: 1, rows: 3, layout: { kpis: { row: 1 }, orders: { row: 2 }, activity: { row: 3 } } },
]Import
import '@c2n/components/dashboard'Usage
Named panel sizes
When the user picks a pane’s size from a fixed set (“small”, “wide”) rather than dragging tracks, declare the set on
the grid with sizes — each name maps to a colSpan and/or rowSpan — and give each card its authored size with
size. setPanelSize(cardId, name) changes one at runtime (null gives the card its authored size back). With a
storage-key the choice is stored alongside the tracks and the card order ({ version: 1, columns, rows, order, breakpoint, sizes }), layout-change reports every card’s size in detail.sizes, and reset() forgets the
choices with the tracks. On restore a payload of another version is discarded, a choice naming an unknown size or a
card that is gone is ignored (the card keeps its authored size), and a layout stored before sizes existed still
restores. A layout record entry with its own span wins over the size.
<c2-dashboard storage-key="ops" columns="2" rows="2" sizes='{"small":{"colSpan":1},"wide":{"colSpan":2}}'>
<c2-dash-card card-id="cpu" col="1" row="1" size="small">…</c2-dash-card>
<c2-dash-card card-id="logs" col="1" row="2" size="wide">…</c2-dash-card>
</c2-dashboard>
<script type="module">
const grid = document.querySelector('c2-dashboard')
grid.addEventListener('layout-change', (event) => console.log(event.detail.sizes)) // { cpu: 'wide', logs: 'wide' }
grid.setPanelSize('cpu', 'wide')
</script>Adding and removing panes
Panes can come and go at runtime. A c2-dash-card appended to a grid that is already on screen fades and scales
in; to take one out with the same motion, either flip it to visible: false in the layout record — it fades, then
hides — or call card.dismiss(), which plays the animation and removes the element once it is done (the promise
resolves when it is gone, which is where a data-driven app drops the row). Cards present at load do not animate,
--c2-dash-card__enter--animation-duration / --c2-dash-card__leave--animation-duration set the timing, and
prefers-reduced-motion turns both off.
Placement stays yours. The grid never re-places cards on its own. A pane that is removed or hidden leaves its
cells empty and its neighbours where they were; a pane added at runtime lands exactly where its col / row say,
over whatever already occupies that cell. If the gap should close, or room should be made, the application decides
how: set the remaining cards’ col / row / col-span / row-span, or hand the grid a layout record keyed by
card-id. Both are applied in place, and the animations run alongside. The demo below does the simple version —
it looks for the first free cell before adding.