Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dashboard][Collapsable Panels] Responsive layout #200771

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
da31420
[Dashboard][Collapsable Panels] Responsive layout
mbondyra Nov 21, 2024
3f272f1
fixes https://github.com/elastic/kibana/pull/200771#discussion_r18530…
mbondyra Nov 22, 2024
d8a85fe
refactor `isRsponsive` and move the subjects
mbondyra Nov 22, 2024
f8cf272
display instead of transform (TBD)
mbondyra Nov 22, 2024
e1f3ade
extract GridRowSummary
mbondyra Nov 22, 2024
cd143d7
separate subscriptions
mbondyra Nov 22, 2024
76fe5ad
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 22, 2024
337c22f
=set Edit default mode
mbondyra Nov 25, 2024
577bae8
reversed display:none in favor of translate for maximizing the panel …
mbondyra Nov 25, 2024
d3793b6
changed the classname from kbnGrid--nonInteractive to kbnGrid--static
mbondyra Nov 25, 2024
faf4263
Merge commit 'fcf319e7c447f3fe5057461a33d4a814ebc6a2c8' into dashboar…
mbondyra Nov 25, 2024
fbaf3de
Merge branch 'main' into dashboard/collapsible_panels/maximize_panel
mbondyra Nov 25, 2024
2e49430
Merge branch 'dashboard/collapsible_panels/maximize_panel' of github.…
mbondyra Nov 25, 2024
24d0b94
make the ref local
mbondyra Nov 25, 2024
600e177
fix scrolling after resizing
mbondyra Nov 25, 2024
4827f5c
Merge commit 'e0607f7fadd0147a1372da62584a585d21e3eda8' into dashboar…
mbondyra Nov 26, 2024
53ee088
divide subscriptions
mbondyra Nov 26, 2024
e073619
update the view/edit mode when switching between the mode in mobile view
mbondyra Nov 26, 2024
6e22deb
separate subscription for a row
mbondyra Nov 27, 2024
5bdbb9f
move subscriptions to a single useEffect
mbondyra Nov 27, 2024
97eb603
naming
mbondyra Nov 27, 2024
17abcc6
GridRowHeader name
mbondyra Nov 27, 2024
5134fcd
fix non-adapting height
mbondyra Nov 27, 2024
1c95802
just some whitespaces
mbondyra Nov 27, 2024
e959031
experiment to check out: https://caniuse.com/?search=fill-available
mbondyra Nov 27, 2024
2a8c9e5
Revert "experiment to check out: https://caniuse.com/?search=fill-ava…
mbondyra Nov 28, 2024
45568de
Merge commit 'ac6025eaa1b5e0e733bab0110b60928e27ee6860' into dashboar…
mbondyra Nov 28, 2024
b68b89d
remove the not-needed code
mbondyra Nov 28, 2024
9583785
using flex outside of the plugin
mbondyra Nov 28, 2024
1029888
a lil refactoring
mbondyra Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 60 additions & 3 deletions examples/grid_example/public/app.tsx
Heenawter marked this conversation as resolved.
Show resolved Hide resolved
Heenawter marked this conversation as resolved.
Show resolved Hide resolved
Heenawter marked this conversation as resolved.
Show resolved Hide resolved
Heenawter marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import {
EuiPageTemplate,
EuiProvider,
EuiSpacer,
EuiButtonGroup,
EuiButtonIcon,
} from '@elastic/eui';
import { AppMountParameters } from '@kbn/core-application-browser';
import { CoreStart } from '@kbn/core-lifecycle-browser';
import { GridLayout, GridLayoutData } from '@kbn/grid-layout';
import { GridLayout, GridLayoutData, GridAccessMode } from '@kbn/grid-layout';
import { i18n } from '@kbn/i18n';

import { getPanelId } from './get_panel_id';
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 [accessMode, setAccessMode] = useState<GridAccessMode>('EDIT');
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 All @@ -107,7 +128,12 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
defaultMessage: 'Grid Layout Example',
})}
/>
<EuiPageTemplate.Section color="subdued">
<EuiPageTemplate.Section
color="subdued"
contentProps={{
css: { display: 'flex', flexFlow: 'column nowrap', flexGrow: 1 },
}}
>
<EuiCallOut
title={i18n.translate('examples.gridExample.sessionStorageCallout', {
defaultMessage:
Expand All @@ -132,6 +158,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 +173,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: '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={accessMode}
onChange={(id) => {
setAccessMode(id as GridAccessMode);
}}
/>
</EuiFlexItem>
{hasUnsavedChanges && (
<EuiFlexItem grow={false}>
<EuiBadge color="warning">
Expand Down Expand Up @@ -190,6 +245,8 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => {
</EuiFlexGroup>
<EuiSpacer size="m" />
<GridLayout
accessMode={accessMode}
expandedPanelId={expandedPanelId}
layout={currentLayout}
gridSettings={{
gutterSize: DASHBOARD_MARGIN_SIZE,
Expand Down
35 changes: 33 additions & 2 deletions packages/kbn-grid-layout/grid/grid_height_smoother.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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';

export const GridHeightSmoother = ({
Expand All @@ -19,11 +20,14 @@ export const GridHeightSmoother = ({
// set the parent div size directly to smooth out height changes.
const smoothHeightRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const subscription = combineLatest([
const interactionStyleSubscription = combineLatest([
gridLayoutStateManager.gridDimensions$,
gridLayoutStateManager.interactionEvent$,
]).subscribe(([dimensions, interactionEvent]) => {
if (!smoothHeightRef.current) return;
if (gridLayoutStateManager.expandedPanelId$.getValue()) {
return;
}
if (!interactionEvent) {
smoothHeightRef.current.style.height = `${dimensions.height}px`;
return;
Expand All @@ -39,7 +43,34 @@ export const GridHeightSmoother = ({
smoothHeightRef.current.getBoundingClientRect().height
)}px`;
});
return () => subscription.unsubscribe();

const expandedPanelSubscription = gridLayoutStateManager.expandedPanelId$.subscribe(
(expandedPanelId) => {
if (!smoothHeightRef.current) return;

if (expandedPanelId) {
const smoothHeightRefY =
smoothHeightRef.current.getBoundingClientRect().y + document.documentElement.scrollTop;
const gutterSize = parseFloat(euiThemeVars.euiSizeL);

// When panel is expanded, ensure the page occupies the full viewport height
// If the parent element is a flex container (preferred approach):
smoothHeightRef.current.style.flexBasis = `100%`;

// fallback in case parent is not a flex container (less reliable if shifts happen after the time we calculate smoothHeightRefY)
smoothHeightRef.current.style.height = `calc(100vh - ${smoothHeightRefY + gutterSize}px`;
smoothHeightRef.current.style.transition = 'none';
} else {
smoothHeightRef.current.style.flexBasis = '';
smoothHeightRef.current.style.height = '';
smoothHeightRef.current.style.transition = '';
}
}
);
return () => {
interactionStyleSubscription.unsubscribe();
expandedPanelSubscription.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Expand Down
21 changes: 20 additions & 1 deletion packages/kbn-grid-layout/grid/grid_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@

import { cloneDeep } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import classNames from 'classnames';
import { 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';
import { GridAccessMode, GridLayoutData, GridSettings } from './types';
import { useGridLayoutEvents } from './use_grid_layout_events';
import { useGridLayoutState } from './use_grid_layout_state';
import { isLayoutEqual } from './utils/equality_checks';
Expand All @@ -24,17 +26,23 @@ interface GridLayoutProps {
gridSettings: GridSettings;
renderPanelContents: (panelId: string) => React.ReactNode;
onLayoutChange: (newLayout: GridLayoutData) => void;
expandedPanelId?: string;
accessMode?: GridAccessMode;
}

export const GridLayout = ({
layout,
gridSettings,
renderPanelContents,
onLayoutChange,
expandedPanelId,
accessMode = 'EDIT',
}: GridLayoutProps) => {
const { gridLayoutStateManager, setDimensionsRef } = useGridLayoutState({
layout,
gridSettings,
expandedPanelId,
accessMode,
});
useGridLayoutEvents({ gridLayoutStateManager });

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

const gridClassNames = classNames('kbnGrid', {
'kbnGrid--static': expandedPanelId || accessMode === 'VIEW',
'kbnGrid--hasExpandedPanel': Boolean(expandedPanelId),
});

return (
<GridHeightSmoother gridLayoutStateManager={gridLayoutStateManager}>
<div
ref={(divElement) => {
setDimensionsRef(divElement);
}}
className={gridClassNames}
css={css`
&.kbnGrid--hasExpandedPanel {
height: 100%;
}
`}
>
{children}
</div>
Expand Down
70 changes: 65 additions & 5 deletions packages/kbn-grid-layout/grid/grid_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { css } from '@emotion/react';
import { euiThemeVars } from '@kbn/ui-theme';

import { GridLayoutStateManager, PanelInteractionEvent } from './types';
import { getKeysInOrder } from './utils/resolve_grid_row';

export const GridPanel = forwardRef<
HTMLDivElement,
Expand Down Expand Up @@ -50,13 +51,21 @@ export const GridPanel = forwardRef<
grid-column-end: ${initialPanel.column + 1 + initialPanel.width};
grid-row-start: ${initialPanel.row + 1};
grid-row-end: ${initialPanel.row + 1 + initialPanel.height};
&.kbnGridPanel--isExpanded {
transform: translate(9999px, 9999px);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
`;
}, [gridLayoutStateManager, rowIndex, panelId]);

useEffect(
() => {
/** Update the styles of the panel via a subscription to prevent re-renders */
const styleSubscription = combineLatest([
const activePanelStyleSubscription = combineLatest([
gridLayoutStateManager.activePanel$,
gridLayoutStateManager.gridLayout$,
gridLayoutStateManager.runtimeSettings$,
Expand All @@ -68,6 +77,7 @@ export const GridPanel = forwardRef<
if (!ref || !panel) return;

const currentInteractionEvent = gridLayoutStateManager.interactionEvent$.getValue();

if (panelId === activePanel?.id) {
// if the current panel is active, give it fixed positioning depending on the interaction event
const { position: draggingPosition } = activePanel;
Expand All @@ -91,7 +101,7 @@ export const GridPanel = forwardRef<
ref.style.gridRowEnd = ``;
} else {
// if the current panel is being dragged, render it with a fixed position + size
ref.style.position = 'fixed';
ref.style.position = `fixed`;
ref.style.left = `${draggingPosition.left}px`;
ref.style.top = `${draggingPosition.top}px`;
ref.style.width = `${draggingPosition.right - draggingPosition.left}px`;
Expand Down Expand Up @@ -121,8 +131,50 @@ export const GridPanel = forwardRef<
}
});

const expandedPanelStyleSubscription = gridLayoutStateManager.expandedPanelId$
.pipe(skip(1)) // skip the first emit because the `initialStyles` will take care of it
.subscribe((expandedPanelId) => {
const ref = gridLayoutStateManager.panelRefs.current[rowIndex][panelId];
const gridLayout = gridLayoutStateManager.gridLayout$.getValue();
const panel = gridLayout[rowIndex].panels[panelId];
if (!ref || !panel) return;

if (expandedPanelId && expandedPanelId === panelId) {
ref.classList.add('kbnGridPanel--isExpanded');
} else {
ref.classList.remove('kbnGridPanel--isExpanded');
}
});

const mobileViewStyleSubscription = gridLayoutStateManager.isMobileView$
.pipe(skip(1))
.subscribe((isMobileView) => {
if (!isMobileView) {
return;
}
const ref = gridLayoutStateManager.panelRefs.current[rowIndex][panelId];
const gridLayout = gridLayoutStateManager.gridLayout$.getValue();
const allPanels = gridLayout[rowIndex].panels;
const panel = allPanels[panelId];
if (!ref || !panel) return;

const sortedKeys = getKeysInOrder(gridLayout[rowIndex]);
const currentPanelPosition = sortedKeys.indexOf(panelId);
const sortedKeysBefore = sortedKeys.slice(0, currentPanelPosition);
const responsiveGridRowStart = sortedKeysBefore.reduce(
(acc, key) => acc + allPanels[key].height,
1
);
ref.style.gridColumnStart = `1`;
ref.style.gridColumnEnd = `-1`;
ref.style.gridRowStart = `${responsiveGridRowStart}`;
ref.style.gridRowEnd = `${responsiveGridRowStart + panel.height}`;
});

return () => {
styleSubscription.unsubscribe();
expandedPanelStyleSubscription.unsubscribe();
mobileViewStyleSubscription.unsubscribe();
activePanelStyleSubscription.unsubscribe();
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -150,7 +202,7 @@ export const GridPanel = forwardRef<
>
{/* drag handle */}
<div
className="dragHandle"
className="kbnGridPanel__dragHandle"
css={css`
opacity: 0;
display: flex;
Expand All @@ -174,6 +226,10 @@ export const GridPanel = forwardRef<
cursor: grabbing;
opacity: 1 !important;
}
.kbnGrid--static & {
opacity: 0 !important;
display: none;
}
`}
onMouseDown={(e) => interactionStart('drag', e)}
onMouseUp={(e) => interactionStart('drop', e)}
Expand All @@ -182,7 +238,7 @@ export const GridPanel = forwardRef<
</div>
{/* Resize handle */}
<div
className="resizeHandle"
className="kbnGridPanel__resizeHandle"
onMouseDown={(e) => interactionStart('resize', e)}
onMouseUp={(e) => interactionStart('drop', e)}
css={css`
Expand All @@ -202,6 +258,10 @@ export const GridPanel = forwardRef<
background-color: ${transparentize(euiThemeVars.euiColorSuccess, 0.05)};
cursor: se-resize;
}
.kbnGrid--static & {
opacity: 0 !important;
display: none;
}
`}
/>
<div
Expand Down
Loading