Skip to content

Commit

Permalink
[Dashboard][kbn-grid-layout] Update styles (elastic#206503)
Browse files Browse the repository at this point in the history
Closes elastic#204060

## Summary

This PR updates the styles used for `kbn-grid-layout` in Dashboard as
shown below.

- **Dragging**

    | Before | After |
    |--------|--------|
|
![image](https://github.com/user-attachments/assets/13161969-3eaf-4dce-bcf4-7b4850215816)
|
![image](https://github.com/user-attachments/assets/d76dc678-6277-4819-b554-f6b66b200c0c)
|
|
![image](https://github.com/user-attachments/assets/84d8d489-2240-4f10-809f-0aa30415f408)
|
![image](https://github.com/user-attachments/assets/573d71ad-71fb-47ab-a34e-66b845ecff67)
|

- **Resizing**

    | Before | After |
    |--------|--------|
|
![image](https://github.com/user-attachments/assets/79dfebd0-538b-4193-9b66-30961e9c7b21)
|
![image](https://github.com/user-attachments/assets/bc66ed35-83c4-4291-8cec-6ae8dda8f006)
|
|
![image](https://github.com/user-attachments/assets/d3fb5643-a77f-416f-9fc3-53af6225782a)
|
![image](https://github.com/user-attachments/assets/df2c65d5-af52-4848-b16c-f9f85abd5d9a)
|

As part of this work, I moved all aesthetic style logic out of the
`kbn-grid-layout` package and added support for Emotion to the
`GridLayout` component instead - this means that the consumer is
responsible for applying styles based on given classes, and
`kbn-grid-layout` is now less opinionated. The only styling kept in the
`kbn-grid-layout` package are those that handle layout-engine specific
functionality (positioning of panels, hiding edit actions in view mode,
etc).

In addition, I also updated the styles used in the grid example app and
added settings for dynamically changing the grid gutter size + row
height:




https://github.com/user-attachments/assets/c2f06db1-7041-412e-b546-86b102cc0770


### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

This PR has minimal risk, since it is primarily style changes.

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
2 people authored and viduni94 committed Jan 23, 2025
1 parent 8903754 commit 6398dc5
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 151 deletions.
170 changes: 138 additions & 32 deletions examples/grid_example/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import deepEqual from 'fast-deep-equal';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import { combineLatest, debounceTime } from 'rxjs';

Expand All @@ -20,9 +20,15 @@ import {
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiPageTemplate,
EuiPopover,
EuiRange,
EuiSpacer,
transparentize,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { AppMountParameters } from '@kbn/core-application-browser';
import { CoreStart } from '@kbn/core-lifecycle-browser';
import { AddEmbeddableButton } from '@kbn/embeddable-examples-plugin/public';
Expand Down Expand Up @@ -53,11 +59,16 @@ export const GridExample = ({
coreStart: CoreStart;
uiActions: UiActionsStart;
}) => {
const { euiTheme } = useEuiTheme();

const savedState = useRef<MockSerializedDashboardState>(getSerializedDashboardState());
const [hasUnsavedChanges, setHasUnsavedChanges] = useState<boolean>(false);
const [currentLayout, setCurrentLayout] = useState<GridLayoutData>(
dashboardInputToGridLayout(savedState.current)
);
const [isSettingsPopoverOpen, setIsSettingsPopoverOpen] = useState(false);
const [gutterSize, setGutterSize] = useState<number>(DASHBOARD_MARGIN_SIZE);
const [rowHeight, setRowHeight] = useState<number>(DASHBOARD_GRID_HEIGHT);

const mockDashboardApi = useMockDashboardApi({ savedState: savedState.current });
const [viewMode, expandedPanelId] = useBatchedPublishingSubjects(
Expand Down Expand Up @@ -111,6 +122,41 @@ export const GridExample = ({
[mockDashboardApi]
);

const customLayoutCss = useMemo(() => {
const gridColor = transparentize(euiTheme.colors.backgroundFilledAccentSecondary, 0.2);
return css`
.kbnGridRow--targeted {
background-position: top calc((var(--kbnGridGutterSize) / 2) * -1px) left
calc((var(--kbnGridGutterSize) / 2) * -1px);
background-size: calc((var(--kbnGridColumnWidth) + var(--kbnGridGutterSize)) * 1px)
calc((var(--kbnGridRowHeight) + var(--kbnGridGutterSize)) * 1px);
background-image: linear-gradient(to right, ${gridColor} 1px, transparent 1px),
linear-gradient(to bottom, ${gridColor} 1px, transparent 1px);
background-color: ${transparentize(euiTheme.colors.backgroundFilledAccentSecondary, 0.1)};
}
.kbnGridPanel--dragPreview {
border-radius: ${euiTheme.border.radius};
background-color: ${transparentize(euiTheme.colors.backgroundFilledAccentSecondary, 0.2)};
transition: opacity 100ms linear;
}
.kbnGridPanel--resizeHandle {
opacity: 0;
transition: opacity 0.2s, border 0.2s;
border-radius: 7px 0 7px 0;
border-bottom: 2px solid ${euiTheme.colors.accentSecondary};
border-right: 2px solid ${euiTheme.colors.accentSecondary};
&:hover,
&:focus {
outline-style: none !important;
opacity: 1;
background-color: ${transparentize(euiTheme.colors.accentSecondary, 0.05)};
}
}
`;
}, [euiTheme]);

return (
<KibanaRenderContextProvider {...coreStart}>
<EuiPageTemplate grow={false} offset={0} restrictWidth={false}>
Expand Down Expand Up @@ -148,38 +194,96 @@ export const GridExample = ({
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<AddEmbeddableButton pageApi={mockDashboardApi} uiActions={uiActions} />
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<AddEmbeddableButton pageApi={mockDashboardApi} uiActions={uiActions} />{' '}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
button={
<EuiButton
iconType="arrowDown"
iconSide="right"
onClick={() => setIsSettingsPopoverOpen(!isSettingsPopoverOpen)}
>
{i18n.translate('examples.gridExample.settingsPopover.title', {
defaultMessage: 'Layout settings',
})}
</EuiButton>
}
isOpen={isSettingsPopoverOpen}
closePopover={() => setIsSettingsPopoverOpen(false)}
>
<>
<EuiFormRow
label={i18n.translate('examples.gridExample.settingsPopover.viewMode', {
defaultMessage: 'View mode',
})}
>
<EuiButtonGroup
legend={i18n.translate('examples.gridExample.layoutOptionsLegend', {
defaultMessage: 'Layout options',
})}
options={[
{
id: 'view',
label: i18n.translate('examples.gridExample.viewOption', {
defaultMessage: 'View',
}),
toolTipContent:
'The layout adjusts when the window is resized. Panel interactivity, such as moving and resizing within the grid, is disabled.',
},
{
id: 'edit',
label: i18n.translate('examples.gridExample.editOption', {
defaultMessage: 'Edit',
}),
toolTipContent:
'The layout does not adjust when the window is resized.',
},
]}
idSelected={viewMode}
onChange={(id) => {
mockDashboardApi.viewMode.next(id);
}}
/>
</EuiFormRow>
<EuiFormRow
label={i18n.translate('examples.gridExample.settingsPopover.gutterSize', {
defaultMessage: 'Gutter size',
})}
>
<EuiRange
min={1}
max={30}
value={gutterSize}
onChange={(e) => setGutterSize(parseInt(e.currentTarget.value, 10))}
showLabels
showValue
/>
</EuiFormRow>
<EuiFormRow
label={i18n.translate('examples.gridExample.settingsPopover.rowHeight', {
defaultMessage: 'Row height',
})}
>
<EuiRange
min={5}
max={30}
step={5}
value={rowHeight}
onChange={(e) => setRowHeight(parseInt(e.currentTarget.value, 10))}
showLabels
showValue
/>
</EuiFormRow>
</>
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButtonGroup
legend={i18n.translate('examples.gridExample.layoutOptionsLegend', {
defaultMessage: 'Layout options',
})}
options={[
{
id: 'view',
label: i18n.translate('examples.gridExample.viewOption', {
defaultMessage: 'View',
}),
toolTipContent:
'The layout adjusts when the window is resized. Panel interactivity, such as moving and resizing within the grid, is disabled.',
},
{
id: 'edit',
label: i18n.translate('examples.gridExample.editOption', {
defaultMessage: 'Edit',
}),
toolTipContent: 'The layout does not adjust when the window is resized.',
},
]}
idSelected={viewMode}
onChange={(id) => {
mockDashboardApi.viewMode.next(id);
}}
/>
</EuiFlexItem>
{hasUnsavedChanges && (
<EuiFlexItem grow={false}>
<EuiBadge color="warning">
Expand Down Expand Up @@ -223,13 +327,14 @@ export const GridExample = ({
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />

<GridLayout
accessMode={viewMode === 'view' ? 'VIEW' : 'EDIT'}
expandedPanelId={expandedPanelId}
layout={currentLayout}
gridSettings={{
gutterSize: DASHBOARD_MARGIN_SIZE,
rowHeight: DASHBOARD_GRID_HEIGHT,
gutterSize,
rowHeight,
columnCount: DASHBOARD_GRID_COLUMN_COUNT,
}}
renderPanelContents={renderPanelContents}
Expand All @@ -241,6 +346,7 @@ export const GridExample = ({
mockDashboardApi.panels$.next(panels);
mockDashboardApi.rows$.next(rows);
}}
css={customLayoutCss}
/>
</EuiPageTemplate.Section>
</EuiPageTemplate>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import React, { useEffect, useRef } from 'react';
import { combineLatest, skip } from 'rxjs';

import { transparentize, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';

import { GridLayoutStateManager } from './types';
Expand All @@ -23,7 +22,6 @@ export const DragPreview = ({
gridLayoutStateManager: GridLayoutStateManager;
}) => {
const dragPreviewRef = useRef<HTMLDivElement | null>(null);
const { euiTheme } = useEuiTheme();

useEffect(
() => {
Expand Down Expand Up @@ -59,12 +57,10 @@ export const DragPreview = ({
return (
<div
ref={dragPreviewRef}
className={'kbnGridPanel--dragPreview'}
css={css`
display: none;
pointer-events: none;
border-radius: ${euiTheme.border.radius};
background-color: ${transparentize(euiTheme.colors.accentSecondary, 0.2)};
transition: opacity 100ms linear;
`}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import { css } from '@emotion/react';
import React, { PropsWithChildren, useEffect, useRef } from 'react';
import { combineLatest, distinctUntilChanged, map } from 'rxjs';
import { combineLatest } from 'rxjs';
import { GridLayoutStateManager } from './types';

export const GridHeightSmoother = ({
Expand All @@ -32,35 +32,19 @@ export const GridHeightSmoother = ({
if (!smoothHeightRef.current || gridLayoutStateManager.expandedPanelId$.getValue()) return;

if (!interactionEvent) {
smoothHeightRef.current.style.height = `${dimensions.height}px`;
smoothHeightRef.current.style.minHeight = `${dimensions.height}px`;
smoothHeightRef.current.style.userSelect = 'auto';
return;
}

smoothHeightRef.current.style.height = `${Math.max(
dimensions.height ?? 0,
smoothHeightRef.current.style.minHeight = `${
smoothHeightRef.current.getBoundingClientRect().height
)}px`;
}px`;
smoothHeightRef.current.style.userSelect = 'none';
});

/**
* This subscription sets global CSS variables that can be used by all components contained within
* this wrapper; note that this is **currently** only used for the gutter size, but things like column
* count could be added here once we add the ability to change these values
*/
const globalCssVariableSubscription = gridLayoutStateManager.runtimeSettings$
.pipe(
map(({ gutterSize }) => gutterSize),
distinctUntilChanged()
)
.subscribe((gutterSize) => {
smoothHeightRef.current?.style.setProperty('--kbnGridGutterSize', `${gutterSize}`);
});

return () => {
interactionStyleSubscription.unsubscribe();
globalCssVariableSubscription.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand All @@ -70,17 +54,14 @@ export const GridHeightSmoother = ({
ref={smoothHeightRef}
className={'kbnGridWrapper'}
css={css`
margin: calc(var(--kbnGridGutterSize) * 1px);
height: 100%;
overflow-anchor: none;
transition: height 500ms linear;
transition: min-height 500ms linear;
&:has(.kbnGridPanel--expanded) {
height: 100% !important;
min-height: 100% !important;
position: relative;
transition: none;
// switch to padding so that the panel does not extend the height of the parent
margin: 0px;
padding: calc(var(--kbnGridGutterSize) * 1px);
}
`}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import classNames from 'classnames';
import { cloneDeep } from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { combineLatest, distinctUntilChanged, filter, map, pairwise, skip } from 'rxjs';
Expand All @@ -31,6 +32,7 @@ export interface GridLayoutProps {
onLayoutChange: (newLayout: GridLayoutData) => void;
expandedPanelId?: string;
accessMode?: GridAccessMode;
className?: string; // this makes it so that custom CSS can be passed via Emotion
}

export const GridLayout = ({
Expand All @@ -40,15 +42,17 @@ export const GridLayout = ({
onLayoutChange,
expandedPanelId,
accessMode = 'EDIT',
className,
}: GridLayoutProps) => {
const layoutRef = useRef<HTMLDivElement | null>(null);
const { gridLayoutStateManager, setDimensionsRef } = useGridLayoutState({
layout,
layoutRef,
gridSettings,
expandedPanelId,
accessMode,
});
useGridLayoutEvents({ gridLayoutStateManager });
const layoutRef = useRef<HTMLDivElement | null>(null);

const [rowCount, setRowCount] = useState<number>(
gridLayoutStateManager.gridLayout$.getValue().length
Expand Down Expand Up @@ -173,8 +177,10 @@ export const GridLayout = ({
layoutRef.current = divElement;
setDimensionsRef(divElement);
}}
className="kbnGrid"
className={classNames('kbnGrid', className)}
css={css`
padding: calc(var(--kbnGridGutterSize) * 1px);
&:has(.kbnGridPanel--expanded) {
${expandedPanelStyles}
}
Expand Down
Loading

0 comments on commit 6398dc5

Please sign in to comment.