GrDashboardItem
Machine-translated from the Russian original, not yet reviewed. Read the original
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/minHare 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#headeror#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
| Need | Take |
|---|---|
| A card outside a grid of widgets | GrCard |
| A metric as a number, without the frame of a widget | GrStatistic |
| The grid itself | GrDashboard |
| A catalogue of widgets to add from | GrDashboardPalette |
`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:
<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.
Install
npm i @feugene/granularity-dashboardImport
import { GrDashboardItem } from '@feugene/granularity-dashboard/components/GrDashboardItem'API
The API for this component has not been generated yet: the showcase generator only covers the core so far. Until it does, the reference lives in the package documentation.
Examples 1
Slots
<script setup lang="ts">
import { ref } from 'vue'
import { GrBadge } from '@feugene/granularity'
import type { GrDashboardResponsiveLayout } from '@feugene/granularity-dashboard'
/**
* Виджет — не рамка вокруг содержимого, а карточка со своей поверхностью:
* заголовок, действия рядом с ним, подвал. Плюс собственные границы размера:
* их знает виджет, а не раскладка.
*/
const breakpoints = { lg: 680, md: 520, sm: 400, xs: 0 }
const cols = { lg: 12, md: 8, sm: 4, xs: 2 }
const layout = ref<GrDashboardResponsiveLayout>({
lg: [
{ id: 'report', x: 0, y: 0, w: 7, h: 3 },
{ id: 'narrow', x: 7, y: 0, w: 5, h: 3 },
{ id: 'fixed', x: 0, y: 3, w: 6, h: 2 },
{ id: 'pinned', x: 0, y: 5, w: 12, h: 2 },
],
})
</script>
<template>
<GrDashboard
v-model:layout="layout"
mode="edit"
:breakpoints="breakpoints"
:cols="cols"
:row-height="72"
>
<!-- Шапка, действия и подвал — три слота вокруг содержимого. -->
<GrDashboardItem item-id="report" title="Отчёт за квартал">
<template #actions>
<GrBadge tone="info" size="sm">черновик</GrBadge>
</template>
<p class="text-[var(--gr-muted-fg)]">
Слот <code>#actions</code> держит то, что относится к заголовку: статус, счётчик, кнопку меню.
</p>
<template #footer>
<span class="text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] text-[var(--gr-muted-fg)]">
Обновлён 14 июля, 09:40
</span>
</template>
</GrDashboardItem>
<!--
Границы объявляет сам виджет: раскладка знает координаты, а «ниже двух
строк я нечитаем» знает только он.
-->
<GrDashboardItem item-id="narrow" title="Не сжимается" :min-w="4" :min-h="2">
<p class="text-[var(--gr-muted-fg)]">
<code>min-w="4"</code> и <code>min-h="2"</code>: уголок растягивания дальше этих границ не пустит —
ни мышью, ни с клавиатуры.
</p>
</GrDashboardItem>
<!-- Размер задан вёрсткой содержимого: менять его нечем и незачем. -->
<GrDashboardItem item-id="fixed" title="Размер фиксирован" :resizable="false">
<p class="text-[var(--gr-muted-fg)]">
<code>:resizable="false"</code>: уголка нет вовсе — ни мышью, ни с клавиатуры. Перетащить виджет
при этом можно.
</p>
</GrDashboardItem>
<!-- Статика: ручек у неё нет вовсе, и соседи её обтекают. -->
<GrDashboardItem item-id="pinned" title="Плановые работы" static>
<p class="text-[var(--gr-muted-fg)]">
Закреплённый виджет не двигается ни сам, ни соседями — перемещение, упёршееся в него, отменяется целиком.
</p>
</GrDashboardItem>
</GrDashboard>
</template>