Skip to content

Commit

Permalink
[Dashboard][Collapsable Panels] Responsive layout
Browse files Browse the repository at this point in the history
  • Loading branch information
mbondyra committed Nov 21, 2024
1 parent a8ea094 commit ae60f56
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 64 deletions.
54 changes: 53 additions & 1 deletion examples/grid_example/public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
EuiPageTemplate,
EuiProvider,
EuiSpacer,
EuiButtonGroup,
EuiButtonIcon,
} from '@elastic/eui';
import { AppMountParameters } from '@kbn/core-application-browser';
import { CoreStart } from '@kbn/core-lifecycle-browser';
Expand All @@ -46,6 +48,8 @@ const DASHBOARD_GRID_COLUMN_COUNT = 48;
export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
const savedState = useRef<MockSerializedDashboardState>(getSerializedDashboardState());
const [hasUnsavedChanges, setHasUnsavedChanges] = useState<boolean>(false);
const [expandedPanelId, setExpandedPanelId] = useState<string | undefined>();
const [isResponsive, setIsResponsive] = useState<boolean>(true);
const [currentLayout, setCurrentLayout] = useState<GridLayoutData>(
dashboardInputToGridLayout(savedState.current)
);
Expand All @@ -72,6 +76,7 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
<div style={{ padding: 8 }}>{id}</div>
<EuiButtonEmpty
onClick={() => {
setExpandedPanelId(undefined);
mockDashboardApi.removePanel(id);
}}
>
Expand All @@ -81,6 +86,7 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
</EuiButtonEmpty>
<EuiButtonEmpty
onClick={async () => {
setExpandedPanelId(undefined);
const newPanelId = await getPanelId({
coreStart,
suggestion: id,
Expand All @@ -92,10 +98,25 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
defaultMessage: 'Replace panel',
})}
</EuiButtonEmpty>
<EuiButtonIcon
iconType={expandedPanelId ? 'minimize' : 'expand'}
onClick={() => setExpandedPanelId((expandedId) => (expandedId ? undefined : id))}
aria-label={
expandedPanelId
? i18n.translate('examples.gridExample.minimizePanel', {
defaultMessage: 'Minimize panel {id}',
values: { id },
})
: i18n.translate('examples.gridExample.maximizePanel', {
defaultMessage: 'Maximize panel {id}',
values: { id },
})
}
/>
</>
);
},
[coreStart, mockDashboardApi]
[coreStart, mockDashboardApi, setExpandedPanelId, expandedPanelId]
);

return (
Expand Down Expand Up @@ -132,6 +153,7 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
<EuiFlexItem grow={false}>
<EuiButton
onClick={async () => {
setExpandedPanelId(undefined);
const panelId = await getPanelId({
coreStart,
suggestion: uuidv4(),
Expand All @@ -146,6 +168,34 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButtonGroup
legend={i18n.translate('examples.gridExample.layoutOptionsLegend', {
defaultMessage: 'Layout options',
})}
options={[
{
id: 'responsive',
label: i18n.translate('examples.gridExample.responsiveLayoutOption', {
defaultMessage: 'Responsive layout',
}),
toolTipContent:
'The layout adjusts when the window is resized. Panel interactivity, such as moving and resizing within the grid, is disabled.',
},
{
id: 'fixed',
label: i18n.translate('examples.gridExample.fixedLayoutOption', {
defaultMessage: 'Fixed layout',
}),
toolTipContent: 'The layout does not adjust when the window is resized.',
},
]}
idSelected={isResponsive ? 'responsive' : 'fixed'}
onChange={(id) => {
setIsResponsive(id === 'responsive');
}}
/>
</EuiFlexItem>
{hasUnsavedChanges && (
<EuiFlexItem grow={false}>
<EuiBadge color="warning">
Expand Down Expand Up @@ -190,6 +240,8 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
</EuiFlexGroup>
<EuiSpacer size="m" />
<GridLayout
isResponsive={isResponsive}
expandedPanelId={expandedPanelId}
layout={currentLayout}
gridSettings={{
gutterSize: DASHBOARD_MARGIN_SIZE,
Expand Down
23 changes: 22 additions & 1 deletion packages/kbn-grid-layout/grid/grid_height_smoother.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
import { css } from '@emotion/react';
import React, { PropsWithChildren, useEffect, useRef } from 'react';
import { combineLatest } from 'rxjs';
import { euiThemeVars } from '@kbn/ui-theme';
import { GridLayoutStateManager } from './types';

const getViewportHeight = () =>
window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

export const GridHeightSmoother = ({
children,
gridLayoutStateManager,
Expand All @@ -22,8 +26,25 @@ export const GridHeightSmoother = ({
const subscription = combineLatest([
gridLayoutStateManager.gridDimensions$,
gridLayoutStateManager.interactionEvent$,
]).subscribe(([dimensions, interactionEvent]) => {
gridLayoutStateManager.expandedPanelId$,
]).subscribe(([dimensions, interactionEvent, expandedPanelId]) => {
if (!smoothHeightRef.current) return;

if (expandedPanelId) {
const viewPortHeight = getViewportHeight();
const smoothHeightRefY = smoothHeightRef.current.getBoundingClientRect().y;

// When panel is expanded, ensure the page occupies the full viewport height, no more, no less, so
// smoothHeight height = viewport height - smoothHeight position - EuiPanel padding.

const height = viewPortHeight - smoothHeightRefY - parseFloat(euiThemeVars.euiSizeL);
smoothHeightRef.current.style.height = height + 'px';
smoothHeightRef.current.style.transition = 'none';
return;
} else {
smoothHeightRef.current.style.transition = '';
}

if (!interactionEvent) {
smoothHeightRef.current.style.height = `${dimensions.height}px`;
return;
Expand Down
45 changes: 44 additions & 1 deletion packages/kbn-grid-layout/grid/grid_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@

import { cloneDeep } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import { combineLatest, distinctUntilChanged, filter, map, pairwise, skip } from 'rxjs';
import classNames from 'classnames';
import {
BehaviorSubject,
combineLatest,
distinctUntilChanged,
filter,
map,
pairwise,
skip,
} from 'rxjs';

import { css } from '@emotion/react';
import { GridHeightSmoother } from './grid_height_smoother';
import { GridRow } from './grid_row';
import { GridLayoutData, GridSettings } from './types';
Expand All @@ -24,17 +34,42 @@ interface GridLayoutProps {
gridSettings: GridSettings;
renderPanelContents: (panelId: string) => React.ReactNode;
onLayoutChange: (newLayout: GridLayoutData) => void;
expandedPanelId?: string;
isResponsive?: boolean;
}

export const GridLayout = ({
layout,
gridSettings,
renderPanelContents,
onLayoutChange,
expandedPanelId,
isResponsive,
}: GridLayoutProps) => {
const expandedPanelId$ = useMemo(
() => new BehaviorSubject<string | undefined>(expandedPanelId),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
useEffect(() => {
expandedPanelId$.next(expandedPanelId);
}, [expandedPanelId, expandedPanelId$]);

const isResponsive$ = useMemo(
() => new BehaviorSubject<boolean>(Boolean(isResponsive)),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);

useEffect(() => {
isResponsive$.next(Boolean(isResponsive));
}, [isResponsive, isResponsive$]);

const { gridLayoutStateManager, setDimensionsRef } = useGridLayoutState({
layout,
gridSettings,
expandedPanelId$,
isResponsive$,
});
useGridLayoutEvents({ gridLayoutStateManager });

Expand Down Expand Up @@ -132,12 +167,20 @@ export const GridLayout = ({
});
}, [rowCount, gridLayoutStateManager, renderPanelContents]);

const gridClassNames = classNames('kbnGrid', {
'kbnGrid--nonInteractive': expandedPanelId || isResponsive,
});

return (
<GridHeightSmoother gridLayoutStateManager={gridLayoutStateManager}>
<div
ref={(divElement) => {
setDimensionsRef(divElement);
}}
className={gridClassNames}
css={css`
height: 100%;
`}
>
{children}
</div>
Expand Down
Loading

0 comments on commit ae60f56

Please sign in to comment.