DashKit
@gravity-ui/dashkit ·

DashKit
Una librería para renderizar cuadrículas de dashboards.
Instalación
npm i @gravity-ui/dashkit @gravity-ui/uikit
Descripción
La librería se utiliza para alinear widgets en una cuadrícula, redimensionarlos, añadir nuevos y eliminarlos. Un widget es un componente de React. Por ejemplo, texto, gráficos e imágenes.
Los nuevos widgets se añaden a través de un sistema de plugins.
Plugins
Los plugins son necesarios para crear widgets personalizados.
Props
type ItemManipulationCallback = (eventData: {
layout: Layout[];
oldItem: Layout;
newItem: Layout;
placeholder: Layout;
e: MouseEvent;
element: HTMLElement;
}) => void;
interface DashKitProps {
config: Config;
editMode: boolean;
onItemEdit: ({id}: {id: string}) => void;
onChange: (data: {config: Config; itemsStateAndParams: ItemsStateAndParams}) => void;
onDrop: (dropProps: ItemDropProps) => void;
onItemMountChange: (item: ConfigItem, state: {isAsync: boolead; isMounted: boolean}) => void;
onItemRender: (item: ConfigItem) => void;
onDragStart?: ItemManipulationCallback;
onDrag?: ItemManipulationCallback;
onDragStop?: ItemManipulationCallback;
onResizeStart?: ItemManipulationCallback;
onResize?: ItemManipulationCallback;
onResizeStop?: ItemManipulationCallback;
defaultGlobalParams: GlobalParams;
globalParams: GlobalParams;
itemsStateAndParams: ItemsStateAndParams;
settings: SettingsProps;
context: ContextProps;
overlayControls?: Record<string, OverlayControlItem[]> | null;
overlayMenuItems?: MenuItems[] | null;
noOverlay?: boolean;
focusable?: boolean;
onItemFocus: (item: ConfigItem) => void;
onItemBlur: (item: ConfigItem) => void;
draggableHandleClassName?: string;
getPreparedCopyItemOptions?: (options: PreparedCopyItemOptions) => PreparedCopyItemOptions;
onCopyFulfill?: (error: null | Error, data?: PreparedCopyItemOptions) => void;
}
- config: Config.
- editMode: Indica si el modo de edición está activado.
- onItemEdit: Se llama al hacer clic para editar un widget.
- onChange: Se llama cuando cambian la configuración o itemsStateAndParams.
- onDrop: Se llama cuando un elemento se suelta desde ActionPanel usando (#DashKitDnDWrapper).
- onItemMountChange: Se llama cuando cambia el estado de montaje de un elemento.
- onItemRender: Se llama cuando finaliza el renderizado de un elemento.
- defaultGlobalParams, globalParams: Parámetros que afectan a todos los widgets. En DataLens,
defaultGlobalParamsson parámetros globales establecidos en la configuración del dashboard.globalParamsson parámetros globales que se pueden establecer en la URL. - itemsStateAndParams: itemsStateAndParams.
- settings: Configuración de DashKit.
- context: Objeto que se pasará a todos los widgets.
- overlayControls: Objeto que reemplaza los controles del widget durante la edición. Si no se transmite, se mostrarán los controles básicos. Si se pasa
null, solo se mostrará el botón de cierre o un menú personalizado. - overlayMenuItems: Elementos de menú desplegable personalizados.
- noOverlay: Si es
true, la superposición y los controles no se mostrarán durante la edición. - focusable: Si es
true, los elementos de la cuadrícula serán enfocables. - onItemFocus: Se llama cuando
focusableestruey un elemento recibe el foco. - onItemBlur: Se llama cuando
focusableestruey un elemento pierde el foco. - draggableHandleClassName: Nombre de la clase CSS del elemento que hace que el widget sea arrastrable.
- onDragStart: Se llama desde ReactGridLayout cuando comienza a arrastrar un elemento.
- onDrag: Se llama desde ReactGridLayout mientras se arrastra un elemento.
- onDragStop: Se llama desde ReactGridLayout cuando se detiene el arrastre de un elemento.
- onResizeStart: Se llama desde ReactGridLayout cuando comienza a redimensionar un elemento.
- onResize: Se llama desde ReactGridLayout mientras se redimensiona un elemento.
- onResizeStop: Se llama desde ReactGridLayout cuando se detiene el redimensionamiento de un elemento.
- getPreparedCopyItemOptions: Se llama para convertir un elemento copiado en un objeto serializable antes de guardarlo en el localStorage. Debe usarse en lugar de la prop obsoleta
context.getPreparedCopyItemOptions. - onCopyFulfill: Se llama cuando la copia de un elemento finaliza con
error=nullydatadefinido en caso de éxito, y conerror: Errorsindataen caso contrario.
Uso
Configuración de DashKit
Antes de usar DashKit como un componente de React, debe configurarse.
-
Establecer el idioma
import {configure, Lang} from '@gravity-ui/uikit'; configure({lang: Lang.En}); -
DashKit.setSettings
Se utiliza para la configuración global de DashKit (como márgenes entre widgets, tamaños de widget predeterminados y menú de superposición de widgets).
import {DashKit} from '@gravity-ui/dashkit'; DashKit.setSettings({ gridLayout: {margin: [8, 8]}, isMobile: true, // menu: [] as Array<MenuItem>, }); -
DashKit.registerPlugins
Registro y configuración de plugins.
import {DashKit} from '@gravity-ui/dashkit'; import {pluginTitle, pluginText} from '@gravity-ui/dashkit'; DashKit.registerPlugins( pluginTitle, pluginText.setSettings({ apiHandler({text}) { return api.getMarkdown(text); }, }), ); DashKit.registerPlugins({ type: 'custom', defaultLayout: { w: 10, h: 8, }, renderer: function CustomPlugin() { return <div>Custom widget with custom controls</div>; }, });
Config
export interface Config {
salt: string; // para formar un ID único
counter: number; // para formar un ID único, solo aumenta
items: ConfigItem[]; // estados iniciales de los widgets
layout: ConfigLayout[]; // posición del widget en la cuadrícula https://github.com/react-grid-layout
aliases: ConfigAliases; // alias para parámetros ver #Params
connections: ConfigConnection[]; // enlaces entre widgets ver #Params
}
Ejemplo de configuración:
import {DashKitProps} from '@gravity-ui/dashkit';
const config: DashKitProps['config'] = {
salt: '0.46703554571365613',
counter: 4,
items: [
{
id: 'tT',
data: {
size: 'm',
text: 'Título',
showInTOC: true,
},
type: 'title',
namespace: 'default',
orderId: 1,
},
{
id: 'Ea',
data: {
text: 'modo _editActive',
_editActive: true,
},
type: 'text',
namespace: 'default',
},
{
id: 'zR',
data: {
text: '### Texto',
},
type: 'text',
namespace: 'default',
orderId: 0,
},
{
id: 'Dk',
data: {
foo: 'bar',
},
type: 'custom',
namespace: 'default',
orderId: 5,
},
],
layout: [
{
h: 2,
i: 'tT',
w: 36,
x: 0,
y: 0,
},
{
h: 6,
i: 'Ea',
w: 12,
x: 0,
y: 2,
},
{
h: 6,
i: 'zR',
w: 12,
x: 12,
y: 2,
},
{
h: 4,
i: 'Dk',
w: 8,
x: 0,
y: 8,
},
],
aliases: {},
connections: [],
};
Añadir un nuevo elemento a la configuración:
const newLayout = updateLayout: [
{
h: 6,
i: 'Ea',
w: 12,
x: 0,
y: 6,
},
{
h: 4,
i: 'Dk',
w: 8,
x: 0,
y: 12,
},
];
const newConfig = DashKit.setItem({
item: {
data: {
text: `Algún texto`,
},
namespace: 'default',
type: 'text',
// Opcional. Si se necesita insertar un nuevo elemento en el layout actual con dimensiones predefinidas
layout: { // El elemento actual se inserta antes de 'Ea'
h: 6,
w: 12,
x: 0,
y: 2,
},,
},
config: config,
options: {
// Opcional. Nuevos valores de layout para elementos existentes cuando se suelta un nuevo elemento desde ActionPanel
updateLayout: newLayout,
},
});
Cambiar un elemento existente en la configuración:
const newConfig = DashKit.setItem({
item: {
id: 'tT', // item.id
data: {
size: 'm',
text: `Nuevo título`,
},
namespace: 'default',
type: 'title',
},
config: config,
});
Eliminar un elemento de la configuración:
import {DashKitProps} from '@gravity-ui/dashkit';
const oldItemsStateAndParams: DashKitProps['itemsStateAndParams'] = {};
const {config: newConfig, itemsStateAndParams} = DashKit.removeItem({
id: 'tT', // item.id
config: config,
itemsStateAndParams: this.state.itemsStateAndParams,
});
Parámetros
type Params = Record<string, string | string[]>;
DashKit genera parámetros según los parámetros predeterminados para widgets, enlaces y alias. Estos parámetros son necesarios para la biblioteca ChartKit.
Orden de generación:
defaultGlobalParams- Parámetros predeterminados del widget
item.default globalParams- Parámetros de itemsStateAndParams según la cola.
itemsStateAndParams
Objeto que almacena los parámetros y estados de los widgets, así como una cola de cambios de parámetros.
Tiene un campo __meta__ para almacenar la cola e información meta.
interface StateAndParamsMeta = {
__meta__: {
queue: {id: string}[]; // cola
version: number; // versión actual de itemsStateAndParams
};
}
Y también estados y parámetros de los widgets:
interface ItemsStateAndParamsBase {
[itemId: string]: {
state?: Record<string, any>;
params?: Params;
};
}
type ItemsStateAndParams = StateAndParamsMeta & ItemsStateAndParamsBase;
Eventos experimentales de DashKit
Experimental: esta API puede cambiar en versiones menores.
DashKit expone una API de eventos de instancia experimental. Utiliza una referencia de componente y suscríbete con dashkitRef.current?.on(eventName, handler). El método devuelve una función de cancelación de suscripción.
El primer evento admitido es change. Se emite cuando el layout cambia, antes de que se llame a onChange. El manejador puede leer los layouts completos siguiente y anterior, leer los parches del layout o llamar a preventDefault() para detener la llamada predeterminada a onChange.
import React from 'react';
import {DashKit} from '@gravity-ui/dashkit';
import type {DashKitChangeEvent} from '@gravity-ui/dashkit';
function Dashboard() {
const dashkitRef = React.useRef<DashKit>(null);
React.useEffect(() => {
const unsubscribe = dashkitRef.current?.on('change', (event: DashKitChangeEvent) => {
console.log(event.patches);
if (event.patches.length > 0) {
event.preventDefault();
}
});
return () => unsubscribe?.();
}, []);
return <DashKit ref={dashkitRef} config={config} editMode={true} onChange={onChange} />;
}
type DashKitLayoutPatch = Pick<ConfigLayout, 'i'> &
Partial<Pick<ConfigLayout, 'x' | 'y' | 'w' | 'h' | 'parent'>>;
type DashKitChangeEvent = {
patches: DashKitLayoutPatch[];
layout: ConfigLayout[];
previousLayout: ConfigLayout[];
preventDefault: () => void;
readonly defaultPrevented: boolean;
};
Actualizaciones de layout basadas en eventos
Si usas preventDefault() en el manejador del evento change, ahora puedes gestionar las actualizaciones del layout sin necesidad de reinicializar la prop config. DashKit mantiene una línea base interna y calcula los parches de forma incremental:
function Dashboard() {
const [config, setConfig] = useState(initialConfig);
const dashkitRef = useRef<DashKit>(null);
useEffect(() => {
const unsubscribe = dashkitRef.current?.on('change', (event) => {
event.preventDefault(); // No llamar a onChange
// Envía solo los parches incrementales a tu backend
sendPatches(event.patches);
// No es necesario llamar a setConfig({ ...config, layout: event.layout })
// DashKit mantiene el estado visual internamente
});
return unsubscribe;
}, []);
return <DashKit ref={dashkitRef} config={config} editMode onChange={() => {}} />;
}
Importante: Si más tarde actualizas config.layout desde las props (por ejemplo, desde una sincronización del servidor), DashKit restablecerá su línea base interna para que coincida con la nueva prop. Esto garantiza la compatibilidad tanto con flujos de trabajo basados en eventos como controlados.
Menú
Puedes especificar un menú superpuesto de widgets personalizado para DashKit en modo de edición
type MenuItem = {
id: string; // id único
title?: string; // título de texto
icon?: ReactNode; // nodo de icono
iconSize?: number | string; // tamaño del icono en px como número o como cadena con unidades
handler?: (item: ConfigItem) => void; // manejador de acción de elemento personalizado
visible?: (item: ConfigItem) => boolean; // manejador de visibilidad opcional para filtrar elementos del menú
className?: string; // propiedad de clase personalizada
};
// usa un array de elementos de menú en la configuración
<Dashkit overlayMenuItems={[] as Array<MenuItem> | null} />
[obsoleto]
// la propiedad overlayMenuItems tiene mayor prioridad que el menú setSettings
DashKit.setSettings({menu: [] as Array<MenuItem>});
Arrastrar y soltar elementos desde ActionPanel
DashKitDnDWrapper
type DraggedOverItem = {
h: number;
w: number;
type: string;
parent: string;
i?: number;
};
interface DashKitDnDWrapperProps {
dragImageSrc?: string;
onDragStart?: (dragProps: ItemDragProps) => void;
onDragEnd?: () => void;
onDropDragOver?: (
draggedItem: DraggedOverItem,
sharedItem: DraggedOverItem | null,
) => void | boolean;
}
- dragImageSrc: Vista previa de la imagen de arrastre, por defecto se usa un png transparente de 1px en base64
- onDragStart: Callback que se llama cuando un elemento se arrastra desde ActionPanel
- onDragEnd: Callback que se llama cuando se suelta el elemento o se cancela el arrastre
type ItemDragProps = {
type: string; // Tipo de plugin
layout?: {
// Opcional. Tamaño del elemento de layout para vista previa e inicialización
w?: number;
h?: number;
};
extra?: any; // Contexto de usuario personalizado
};
type ItemDropProps = {
commit: () => void; // Callback que debe llamarse después de que se realicen todas las operaciones de configuración
dragProps: ItemDragProps; // Props de arrastre del elemento
itemLayout: ConfigLayout; // Dimensiones del layout del elemento calculadas
newLayout: ConfigLayout[]; // Nuevo layout después de soltar el elemento
};
Ejemplo:
const overlayMenuItems = [
{
id: 'chart',
icon: <Icon data={ChartColumn} />,
title: 'Chart',
qa: 'chart',
dragProps: { // ItemDragProps
type: 'custom', // Tipo de plugin registrado
},
}
]
const onDrop = (dropProps: ItemDropProps) => {
// ... añade el elemento a tu configuración
dropProps.commit();
}
<DashKitDnDWrapper>
<DashKit editMode={true} config={config} onChange={onChange} onDrop={onDrop} />
<ActionPanel items={overlayMenuItems} />
</DashKitDnDWrapper>
API CSS
| Nombre | Descripción |
|---|---|
| Variables del panel de acciones | |
--dashkit-action-panel-color | Color de fondo |
--dashkit-action-panel-border-color | Color del borde |
--dashkit-action-panel-border-radius | Radio del borde |
| Variables del elemento del panel de acciones | |
--dashkit-action-panel-item-color | Color de fondo |
--dashkit-action-panel-item-text-color | Color del texto |
--dashkit-action-panel-item-color-hover | Color de fondo al pasar el ratón |
--dashkit-action-panel-item-text-color-hover | Color del texto al pasar el ratón |
| Variables de superposición | |
--dashkit-overlay-border-color | Color del borde |
--dashkit-overlay-color | Color de fondo |
--dashkit-overlay-opacity | Opacidad |
| Variables del elemento de la cuadrícula | |
--dashkit-grid-item-edit-opacity | Opacidad |
--dashkit-grid-item-border-radius | Radio del borde |
| Variables del marcador de posición | |
--dashkit-placeholder-color | Color de fondo |
--dashkit-placeholder-opacity | Opacidad |
Ejemplo de uso
.custom-theme-wrapper {
--dashkit-grid-item-edit-opacit: 1;
--dashkit-overlay-color: var(--g-color-base-float);
--dashkit-overlay-border-color: var(--g-color-base-float);
--dashkit-overlay-opacity: 0.5;
--dashkit-action-panel-border-color: var(--g-color-line-info);
--dashkit-action-panel-color: var(--g-color-base-float-accent);
--dashkit-action-panel-border-radius: var(--g-border-radius-xxl);
}
// ....
const CustomThemeWrapper = (props: {
dashkitProps: DashkitProps;
actionPanelProps: ActionPanelProps;
}) => {
return (
<div className="custom-theme-wrapper">
<Dashkit {...props.dashkitProps} />
<ActionPanel {...props.actionPanelProps} />
</div>
);
};
Desarrollo
Compilación y observación
- Compilar dependencias
npm ci - Compilar proyecto
npm run build - Compilar storybook
npm run start
Por defecto, storybook se ejecuta en http://localhost:7120/.
Los cambios nuevos en un proyecto no siempre se detectan cuando storybook está en ejecución, por lo que es mejor recompilar un proyecto manualmente y reiniciar storybook.
Ejemplo de configuración de nginx para desarrollo en una máquina de desarrollo
server {
server_name dashkit.username.ru;
include common/ssl;
access_log /home/username/logs/common.access.log;
error_log /home/username/logs/common.error.log;
root /home/username/projects/dashkit;
location / {
try_files $uri @node;
}
location @node {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:7120;
proxy_redirect off;
}
}
Licencia
Distribuido bajo la Licencia MIT. Ver LICENSE para más detalles.
Para agentes de IA
Un compositor de cuadrículas de paneles que organiza widgets redimensionables y arrastrables en una cuadrícula responsiva a través de un sistema de plugins: recurre a él cuando crees un panel editable por el usuario (agregar/mover/redimensionar/eliminar widgets) en lugar de colocar gráficos o paneles individuales manualmente.
Cuándo usar
- Renderizar un panel configurable donde los widgets se posicionan, redimensionan y reorganizan en una cuadrícula (basado en
react-grid-layout). - Diseños editables por el usuario: agregar/eliminar widgets de un panel de acciones, arrastrar y soltar, modo de edición con controles superpuestos.
- Widgets basados en plugins donde cada tipo de widget (título, texto, gráfico, personalizado) se registra una vez y se controla mediante una
config.
Cuándo no usar
- Para un único gráfico o panel fijo, usa
@gravity-ui/chartso@gravity-ui/chartkitdirectamente: la maquinaria de cuadrícula/plugins es una sobrecarga para un solo widget. - Para una cuadrícula responsiva de propósito general que no sea un panel de widgets, usa
react-grid-layoutdirectamente. - Para incrustar widgets de gráficos basados en ChartKit dentro de un panel DashKit, DashKit es el "shell"; aún depende de
@gravity-ui/chartkitpara renderizar los gráficos reales.
Errores comunes
- Componente
<Dashboard>inexistente — la exportación es<DashKit>(el shell de arrastrar y soltar es<DashKitDnDWrapper>que envuelve<DashKit>+<ActionPanel>). - Mutar
configen lugar de usar ayudantes — usa los ayudantes estáticosDashKit.setItem({...})/DashKit.removeItem({...})para agregar/cambiar/eliminar elementos, de modo que el diseño y los IDs se mantengan consistentes. - Olvidar
DashKit.setSettings/DashKit.registerPlugins— el componente debe configurarse (idioma, configuración de la cuadrícula, registro de plugins) antes de renderizarse, o los widgets no mostrarán nada. - Confundir las dos props de parámetros —
defaultGlobalParams(valores predeterminados a nivel de panel) vsglobalParams(globales que se pueden anular por URL); ambos fluyen a la cola de generación de parámetros consumida por ChartKit. - Llamar a
onChangemanualmente con el eventochange— cuando usasevent.preventDefault()en el manejador experimentalchange, DashKit mantiene el estado visual internamente; restable