GrDashboardItem

  • dashboard
  • dashboard
  • API · 15 props
import { GrDashboardItem } from '@feugene/granularity-dashboard/components/GrDashboardItem'

Overview

When to take it

  • any widget inside GrDashboard — outside the grid the component makes no sense: moving, stretching and the keyboard go through its context;
  • the content has a minimum — minW/minH are declared here rather than in the layout: the layout knows the coordinates, and “a chart below three rows is unreadable” is known by the widget;
  • a header is not always needed — it appears only if there is a title, a #header or #actions; a map and a large figure have no use for a header;
  • one widget behaves differently — :draggable="false" or :resizable="false" narrow the rule of the grid for it alone.

When to take something else

NeedTake
A card outside a grid of widgetsGrCard
A metric as a number, without the frame of a widgetGrStatistic
The grid itselfGrDashboard
A catalogue of widgets to add fromGrDashboardPalette

Examples 3 each with a live demo and its code

  • Every part of a widget

    title names the widget in the header, in the handle names and in the grid’s announcements. #actions are product buttons and are always there; #editActions and show-settings appear only in edit mode, without adding a header; #footer stays at the bottom while the body scrolls; #skeleton replaces the body while «Refresh» loads.

    Support today
    Waiting longest
    • Invoice shows the old VAT numberT-229142 min
    • Cannot add a second adminT-22941 h 10 min
    • Card declined on renewalT-22972 h 05 min
    • Export to DATEV is emptyT-23013 h 40 min
    Updated 2 min ago
    Agents online
    7 / 9Two on a break until 14:30
    Switch to Edit to see the handle, Remove and the gear.
  • A chart edge to edge, a list that scrolls, a number without a header

    padding="none" with overflow="hidden" gives the body to the chart edge to edge and removes both the scroll bar and the Tab stop. The default body scrolls, and its scroll is a Tab stop, so a list that does not fit stays reachable. A widget without title, #header or #actions has no header at all — a big number needs no frame.

    Traffic, today
    Chart data
    XRequests per second
    00:00180
    01:00180
    02:00180
    03:00180
    04:00180
    05:00180
    06:00180
    07:00207
    08:00234
    09:00258
    10:00279
    11:00296
    12:00309
    13:00317
    14:00320
    15:00317
    16:00309
    17:00296
    18:00279
    19:00258
    20:00234
    21:00207
    22:00180
    23:00180
    99.982%Uptime this month
    Last errors
    • 502 on /v1/invoices · 14:02
    • Timeout to the bank API · 13:47
    • 429 from the mail provider · 13:15
    • 502 on /v1/payments · 12:58
    • Timeout to the bank API · 12:30
  • A pinned banner and a timeline that grows

    static is about the layout, so the status banner stays put in edit mode too, and nothing pushes it. «Next event» adds to the auto-height timeline: it asks the grid for rows by itself, and the grid reports that as item-auto-resize — separate from item-resize, so data arriving does not look like the user editing the layout. min-w and max-h are the widget’s own limits.

    Resized by hand: 0 · by content: 0
    INC-0912 · Invoices API returning 502 · started 14:02
    Timeline
    1. 14:02 · Alert: 502 rate above 2 %
    2. 14:05 · Lena acknowledged
    Owner
    Lena Brandt · on call until 18:00

API generated from the package · cannot drift

Props

PropTypedefaultDescription
draggableboolean | undefinedundefinedWhether this widget can be dragged and resized. Unset — the grid rule applies; `static` beats both, because it is about the layout, not about the interface.
resizableboolean | undefinedundefinedWhether this widget can be resized by the corner. Not set — the rule of the grid applies.
size"lg" | "xs" | "sm" | "md" | undefinedundefinedThe size step: the type size of the header and the paddings of the header, the body and the footer. `md` by default; taken from `GrConfigProvider`.
paddingGrDashboardItemPadding | undefinedundefinedThe content padding. Unset — a step derived from `size`; `none` gives the widget to its content entirely, edge to edge.
overflowGrDashboardItemOverflow | undefinedundefined`hidden` removes both the scrollbar and the `Tab` stop on the body.
autoHeightboolean | undefinedundefinedThe widget height is set by its content, not by the layout record. Rounded up to a whole row: the grid is integral, and a few pixels of emptiness at the bottom beat a cut-off last line. `minH`/`maxH` still apply, and the corner of such a widget changes the width only.
staticboolean | undefinedundefinedDoes not move by itself and is not moved by its neighbours.
showSettingsboolean | undefinedfalseA settings button among the edit mode actions. It does not turn the header on.
ariaLabelstring | undefined—The accessible name of the widget instead of `title`. Without either, the widget stays an ordinary block, with no `group` role.
itemIdrequiredstring—The widget identifier. Links the markup to the layout record.
titlestring | undefined—The widget name: the heading, the name of the handles, the name in announcements.
minWnumber | undefined—Size bounds. The layout may not contain them — the widget knows them.
minHnumber | undefined—The minimum height of the widget in grid rows.
maxWnumber | undefined—The maximum width of the widget in grid columns.

Slots

SlotTypeDescription
defaultanyThe content of the widget; with `lazy` on the grid it is mounted once it enters the viewport.
headeranyA custom header instead of the `title` heading; it switches the header on.
actionsanyProduct buttons in the header — always visible, and they switch the header on.
editActionsanyEdit mode actions: remove the widget, open the settings.
footeranyThe footer of the widget card.
skeletonanyA placeholder in place of the content until a lazy widget enters the viewport.

Events

EventSignatureDescription
settings(event: "settings", id: string): void—

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrDashboardItemConfigurableProps, GrDashboardItemSize, GrDashboardItemPadding } from '@feugene/granularity-dashboard/components/GrDashboardItem'

Filedist/types/components/GrDashboardItem/config.d.ts

grDashboardItemConfigconst
const grDashboardItemConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrDashboardItem">;

Filedist/types/components/GrDashboardItem/defaults.d.ts

GrDashboardItemConfigurablePropsinterface
interface GrDashboardItemConfigurableProps {
    size: GrDashboardItemSize;
    padding: GrDashboardItemPadding;
    overflow: GrDashboardItemOverflow;
    autoHeight: boolean;
}

Filedist/types/components/GrDashboardItem/grDashboardItemStyles.d.ts

GrDashboardItemSizetype
type GrDashboardItemSize = GrComponentSize;
GrDashboardItemPaddingtype
type GrDashboardItemPadding = 'none' | GrDashboardItemSize;
GrDashboardItemOverflowtype
type GrDashboardItemOverflow = 'auto' | 'hidden';

Filedist/types/components/GrDashboardItem/safelist.d.ts

grDashboardItemSafelistconst
const grDashboardItemSafelist: string[];

Filedist/types/components/GrDashboardItem/GrDashboardItem.vue.d.ts

GrDashboardItemPropsinterface
interface GrDashboardItemProps {
    itemId: string;
    title?: string;
    size?: GrDashboardItemSize;
    padding?: GrDashboardItemPadding;
    overflow?: GrDashboardItemOverflow;
    autoHeight?: boolean;
    draggable?: boolean;
    resizable?: boolean;
    static?: boolean;
    showSettings?: boolean;
    minW?: number;
    minH?: number;
    maxW?: number;
    maxH?: number;
    ariaLabel?: string;
}
GrDashboardItemEmitsinterface
interface GrDashboardItemEmits {
    (e: 'settings', id: string): void;
}

Usage from the library prose

static and draggable are about different things

static is about the layout: the widget does not move by itself and does not let its neighbours move it; a collision with it cancels someone else’s move.

draggable and resizable are about the interface, and they are removed separately: :resizable="false" removes the resize corner while leaving the moving.

The prohibition is checked by the grid itself rather than merely hiding the handle. A protection that rests on “the button was not rendered” would be removed by the very first direct call from the keyboard context.

The handle takes up no room in the flow

In the edit mode the header does not appear for the sake of the handle — the panel lies over the top of the content. Switching the mode therefore shifts nothing: the widget stays exactly in its place rather than jumping by the height of a header.

The hidden panel stays in the DOM and in the tab order: removing it would mean removing the handle from the keyboard walk. Where there is no hovering (a finger), it is visible for the whole of the editing.

Auto height: the content decides how many rows to take

auto-height relieves the application of the task of guessing h on the user’s behalf. The widget measures its content and asks the grid for as many rows as it needs:

vue
<GrDashboardItem item-id="log" title="Events" auto-height>
  <ul><li v-for="e in events" :key="e.id">{{ e.text }}</li></ul>
</GrDashboardItem>

What is measured is the wrapper around the content rather than the body of the widget. The height of the body is set by the cell of the grid, and its scrollHeight equals max(content, container): the widget would be able to grow to fit its content, but having become taller than it, it would report its own height — and would never shrink back. The wrapper appears in the markup only with the prop switched on: widgets without it keep their previous DOM.

Rounding upwards. A row of the grid is a whole one (model.md, invariant 1), and content of 131 pixels with a row of 60 takes three rows rather than two. An emptiness of a few pixels at the bottom is noticeable and harmless — a truncated last row of a table is not. That is how auto height differs from stretching by a corner: there the user drags and expects snapping to the nearest cell, so there the rounding is to the nearest.

The corner of such a widget changes only the width. A height set by hand would be wiped out by the very first measurement, so the vertical is taken away from the gesture and from the arrows at once — rather than promising a movement and cancelling it.

minH and maxH keep applying. If maxH has been reached, the content scrolls by the ordinary rules of overflow, which is what it exists for. static does not yield to auto height at all: it is about the widget not moving either by itself or under pressure from its neighbours.

The grid reports that with a separate event. A change of height travels into update:layout like any other — the layout remains the only truth — but the grid additionally emits itemAutoResize. An application that judges the layout dirty by the edits would otherwise ask “save the changes?” after data has loaded into a widget.

On the server there is no measurement: the h from the layout applies, and the refinement arrives after mounting — as does the breakpoint (see ../ssr.md).

The actions are separated by their lifetime

#actions are product buttons, always visible, and they switch the header on. #editActions are the actions of the edit mode (delete the widget, open the settings): they appear only in mode="edit" and travel either into the header or into the panel if there is no header.

The settings in that row are not a slot but a prop: showSettings draws a gear button in the same place as #editActions and emits settings; the grid forwards that outside as itemSettings. The place, the icon and the accessible name are the same on every dashboard, and what to show on a press is the business of the application: there is a ready window, GrDashboardItemSettings.

The button does not switch the header on, unlike #actions. Otherwise switching the mode would shift the content of a headless widget by its height — exactly what the drag handle moved into the panel over the content for.