mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Expandable Flyout] - customize default right, left and preview widths for push mode (#206155)
## Summary This PR is making some changes to the Expandable Flyout package. Prior work had added [push mode](https://github.com/elastic/kibana/pull/182615) to the package, added [custom way](https://github.com/elastic/kibana/pull/170078) to handle the width for multiple resolutions, and [added support](https://github.com/elastic/kibana/pull/192906) for the internal section to be resiable by users. This PR improves the default user experience when using the flyout in push mode. Until now, the default `right`, `left` and `preview` width in `push` mode and `overlay` mode were identical. This meant that the flyout rendered in `push` mode was most of the time using the whole screen, not leaving any room to the rest of the page content (like the alerts table). The `push` widths are now calculated in a different way, to leave as much room as possible while still allowing the flyout `right` and `left` sections to render their content correctly, at least most of the time. Users can still resize the whole flyout as well as the internal `right` and `left` sections. The `push` widths are generally smaller/narrower than the `overlay` widths. #### The `overlay` mode default widths have not changed https://github.com/user-attachments/assets/28b6c41e-b12c-45cf-aa3e-026a7acdb7b3 #### The `push` mode default widths https://github.com/user-attachments/assets/93706f9e-212b-4cb4-8748-552f2daed585 ### Checklist - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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
This commit is contained in:
parent
ebb31d249f
commit
e7140ff25f
11 changed files with 411 additions and 161 deletions
|
@ -5,6 +5,7 @@
|
||||||
This package offers an expandable flyout UI component and a way to manage the data displayed in it. The component leverages the [EuiFlyout](https://github.com/elastic/eui/tree/main/src/components/flyout) from the EUI library.
|
This package offers an expandable flyout UI component and a way to manage the data displayed in it. The component leverages the [EuiFlyout](https://github.com/elastic/eui/tree/main/src/components/flyout) from the EUI library.
|
||||||
|
|
||||||
The flyout is composed of 3 sections:
|
The flyout is composed of 3 sections:
|
||||||
|
|
||||||
- a right section (primary section) that opens first
|
- a right section (primary section) that opens first
|
||||||
- a left wider section to show more details
|
- a left wider section to show more details
|
||||||
- a preview section, that overlays the right section. This preview section can display multiple panels one after the other and displays a `Back` button
|
- a preview section, that overlays the right section. This preview section can display multiple panels one after the other and displays a `Back` button
|
||||||
|
@ -13,14 +14,14 @@ The flyout is composed of 3 sections:
|
||||||
|
|
||||||
## Design decisions
|
## Design decisions
|
||||||
|
|
||||||
The expandable-flyout is making some strict UI design decisions:
|
The expandable-flyout offers 2 render modes: push and overlay (leveraged from the use of the see [EUI](https://eui.elastic.co/#/layout/flyout#push-versus-overlay)).
|
||||||
- when in collapsed mode (i.e. when only the right/preview section is open), the flyout's width linearly grows from its minimum value of 380px to its maximum value of 750px
|
|
||||||
- when in expanded mode (i.e. when the left section is opened), the flyout's width changes depending on the browser's width:
|
The flyout offers 2 sets of default widths: one for overlay mode and one for push mode. Those width are calculated based on the width of the brower window, and define the default values to be used to render the right, left and preview sections, in a way that is aesthetically pleasing. You can find the details of the calculations [here](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/packages/expandable-flyout/src/hooks/use_window_width.ts);
|
||||||
- if the window is smaller than 1600px, the flyout takes the entire browser window (minus 48px of padding on the left)
|
|
||||||
- for windows bigger than 1600px, the flyout's width is 80% of the entire browser window (with a max width of 1500px for the left section, and 750px for the right section)
|
|
||||||
|
|
||||||
> While the expandable-flyout will work on very small screens, having both the right and left sections visible at the same time will not be a good experience to the user. We recommend only showing the right panel, and therefore handling this situation when you build your panels by considering hiding the actions that could open the left panel (like the expand details button in the [FlyoutNavigation](https://github.com/elastic/kibana/tree/main/x-pack/solutions/security/plugins/security_solution/public/flyout/shared/components/flyout_navigation.tsx)).
|
> While the expandable-flyout will work on very small screens, having both the right and left sections visible at the same time will not be a good experience to the user. We recommend only showing the right panel, and therefore handling this situation when you build your panels by considering hiding the actions that could open the left panel (like the expand details button in the [FlyoutNavigation](https://github.com/elastic/kibana/tree/main/x-pack/solutions/security/plugins/security_solution/public/flyout/shared/components/flyout_navigation.tsx)).
|
||||||
|
|
||||||
|
The flyout also offers a way to the users to change the width of the different sections. These are saved separately from the default widths mentioned above, and the users can always reset back to the default using the gear menu (see the `Optional properties` section below).
|
||||||
|
|
||||||
## State persistence
|
## State persistence
|
||||||
|
|
||||||
The expandable flyout offers 2 ways of managing its state:
|
The expandable flyout offers 2 ways of managing its state:
|
||||||
|
@ -31,7 +32,7 @@ The default behavior saves the state of the flyout in memory. The state is inter
|
||||||
|
|
||||||
### Url storage
|
### Url storage
|
||||||
|
|
||||||
The second way (done by setting the `urlKey` prop to a string value) saves the state of the flyout in the url. This allows the flyout to be automatically reopened when users refresh the browser page, or when users share an url. The `urlKey` will be used as the url parameter.
|
The second way (done by setting the `urlKey` prop to a string value) saves the state of the flyout in the url. This allows the flyout to be automatically reopened when users refresh the browser page, or when users share an url. The `urlKey` will be used as the url parameter.
|
||||||
|
|
||||||
**_Note: the word `memory` cannot be used as an `urlKey` as it is reserved for the memory storage behavior. You can use any other string value, try to use something that should be unique._**
|
**_Note: the word `memory` cannot be used as an `urlKey` as it is reserved for the memory storage behavior. You can use any other string value, try to use something that should be unique._**
|
||||||
|
|
||||||
|
@ -39,7 +40,6 @@ The second way (done by setting the `urlKey` prop to a string value) saves the s
|
||||||
>
|
>
|
||||||
> A good solution is for example to have one instance of a flyout at a page level, and then have multiple panels that can be opened in that flyout.
|
> A good solution is for example to have one instance of a flyout at a page level, and then have multiple panels that can be opened in that flyout.
|
||||||
|
|
||||||
|
|
||||||
## Package API
|
## Package API
|
||||||
|
|
||||||
The ExpandableFlyout [React component](https://github.com/elastic/kibana/tree/main/x-pack/solutions/security/packages/expandable-flyout/src/index.tsx) renders the UI, leveraging an [EuiFlyout](https://eui.elastic.co/#/layout/flyout).
|
The ExpandableFlyout [React component](https://github.com/elastic/kibana/tree/main/x-pack/solutions/security/packages/expandable-flyout/src/index.tsx) renders the UI, leveraging an [EuiFlyout](https://eui.elastic.co/#/layout/flyout).
|
||||||
|
@ -49,6 +49,7 @@ To retrieve the flyout's layout (left, right and preview panels), you can utiliz
|
||||||
To control (or mutate) flyout's layout, you can utilize [useExpandableFlyoutApi](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/packages/expandable-flyout/src/hooks/use_expandable_flyout_api.ts).
|
To control (or mutate) flyout's layout, you can utilize [useExpandableFlyoutApi](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/packages/expandable-flyout/src/hooks/use_expandable_flyout_api.ts).
|
||||||
|
|
||||||
**Expandable Flyout API** exposes the following methods:
|
**Expandable Flyout API** exposes the following methods:
|
||||||
|
|
||||||
- **openFlyout**: open the flyout with a set of panels
|
- **openFlyout**: open the flyout with a set of panels
|
||||||
- **openRightPanel**: open a right panel
|
- **openRightPanel**: open a right panel
|
||||||
- **openLeftPanel**: open a left panel
|
- **openLeftPanel**: open a left panel
|
||||||
|
@ -59,13 +60,14 @@ To control (or mutate) flyout's layout, you can utilize [useExpandableFlyoutApi]
|
||||||
- **previousPreviewPanel**: navigate to the previous preview panel
|
- **previousPreviewPanel**: navigate to the previous preview panel
|
||||||
- **closeFlyout**: close the flyout
|
- **closeFlyout**: close the flyout
|
||||||
|
|
||||||
> The expandable flyout propagates the `onClose` callback from the EuiFlyout component. As we recommend having a single instance of the flyout in your application, it's up to the application's code to dispatch the event (through Redux, window events, observable, prop drilling...).
|
> The expandable flyout propagates the `onClose` callback from the EuiFlyout component. As we recommend having a single instance of the flyout in your application, it's up to the application's code to dispatch the event (through Redux, window events, observable, prop drilling...).
|
||||||
|
|
||||||
When calling `openFlyout`, the right panel state is automatically appended in the `history` slice in the redux context. To access the flyout's history, you can use the [useExpandableFlyoutHistory](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/packages/expandable-flyout/src/hooks/use_expandable_flyout_history.ts) hook.
|
When calling `openFlyout`, the right panel state is automatically appended in the `history` slice in the redux context. To access the flyout's history, you can use the [useExpandableFlyoutHistory](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/packages/expandable-flyout/src/hooks/use_expandable_flyout_history.ts) hook.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
To use the expandable flyout in your plugin, first you need wrap your code with the [context provider](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/packages/expandable-flyout/src/context.tsx) at a high enough level as follows:
|
To use the expandable flyout in your plugin, first you need wrap your code with the [context provider](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/packages/expandable-flyout/src/context.tsx) at a high enough level as follows:
|
||||||
|
|
||||||
```typescript jsx
|
```typescript jsx
|
||||||
// state stored in the url
|
// state stored in the url
|
||||||
<ExpandableFlyoutProvider urlKey={'myUrlKey'}>
|
<ExpandableFlyoutProvider urlKey={'myUrlKey'}>
|
||||||
|
@ -84,22 +86,26 @@ Then use the [React UI component](https://github.com/elastic/kibana/tree/main/x-
|
||||||
```typescript jsx
|
```typescript jsx
|
||||||
<ExpandableFlyout registeredPanels={myPanels} />
|
<ExpandableFlyout registeredPanels={myPanels} />
|
||||||
```
|
```
|
||||||
|
|
||||||
_where `myPanels` is a list of all the panels that can be rendered in the flyout_
|
_where `myPanels` is a list of all the panels that can be rendered in the flyout_
|
||||||
|
|
||||||
## Optional properties
|
## Optional properties
|
||||||
|
|
||||||
The expandable flyout now offers a way for users to change some of the flyout's UI properties. These are done via a gear icon in the top right corner of the flyout, to the left of the close icon.
|
The expandable flyout now offers a way for users to change some of the flyout's UI properties. These are done via a gear icon in the top right corner of the flyout, to the left of the close icon.
|
||||||
|
|
||||||
The gear icon can be hidden by setting the `hideSettings` property to `true` in the flyout's custom props.
|
|
||||||
The `typeDisabled` property allows to disable the push/overlay toggle.
|
|
||||||
```typescript
|
```typescript
|
||||||
flyoutCustomProps?: {
|
flyoutCustomProps ? : {
|
||||||
hideSettings?: boolean;
|
hideSettings? : boolean;
|
||||||
typeDisabled?: boolean,
|
pushVsOverlay? : { disabled: boolean; tooltip: string; };
|
||||||
|
resize? : { disabled: boolean; tooltip: string; };
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
At the moment, clicking on the gear icon opens a popover that allows you to toggle the flyout between `overlay` and `push` modes (see [EUI](https://eui.elastic.co/#/layout/flyout#push-versus-overlay)). The default value is `overlay`. The package remembers the selected value in local storage, only for expandable flyout that have a urlKey. The state of memory flyouts is not persisted.
|
The gear icon can be hidden by setting the `hideSettings` property to `true` in the flyout's custom props. When shown, clicking on the gear icon opens a popover with the other options rendered in it.
|
||||||
|
|
||||||
|
The `pushVsOverlay` property allows to disable the push/overlay toggle and when enabled allows users to switch between the 2 modes (see [EUI](https://eui.elastic.co/#/layout/flyout#push-versus-overlay)). The default value is `overlay`. The package remembers the selected value in local storage, only for expandable flyout that have a urlKey. The state of memory flyouts is not persisted.
|
||||||
|
|
||||||
|
The `resize` property allow to disable the `Reset size` button and when enabled allows users to reset all the widths to the default (see calculations [here](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/packages/expandable-flyout/src/hooks/use_window_width.ts)).
|
||||||
|
|
||||||
## Terminology
|
## Terminology
|
||||||
|
|
||||||
|
|
|
@ -153,18 +153,21 @@ export const Container: React.FC<ContainerProps> = memo(
|
||||||
|
|
||||||
const flyoutWidth = useMemo(() => {
|
const flyoutWidth = useMemo(() => {
|
||||||
if (showCollapsed) {
|
if (showCollapsed) {
|
||||||
return flyoutWidths.collapsedWidth || defaultWidths.rightWidth;
|
return flyoutWidths.collapsedWidth || defaultWidths[type].rightWidth;
|
||||||
}
|
}
|
||||||
if (showExpanded) {
|
if (showExpanded) {
|
||||||
return flyoutWidths.expandedWidth || defaultWidths.rightWidth + defaultWidths.leftWidth;
|
return (
|
||||||
|
flyoutWidths.expandedWidth ||
|
||||||
|
defaultWidths[type].rightWidth + defaultWidths[type].leftWidth
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
showCollapsed,
|
defaultWidths,
|
||||||
showExpanded,
|
|
||||||
flyoutWidths.collapsedWidth,
|
flyoutWidths.collapsedWidth,
|
||||||
flyoutWidths.expandedWidth,
|
flyoutWidths.expandedWidth,
|
||||||
defaultWidths.rightWidth,
|
showCollapsed,
|
||||||
defaultWidths.leftWidth,
|
showExpanded,
|
||||||
|
type,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// callback function called when user changes the flyout's width
|
// callback function called when user changes the flyout's width
|
||||||
|
|
|
@ -10,14 +10,19 @@ import {
|
||||||
EuiButtonIcon,
|
EuiButtonIcon,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
|
EuiSplitPanel,
|
||||||
EuiText,
|
EuiText,
|
||||||
useEuiTheme,
|
useEuiTheme,
|
||||||
EuiSplitPanel,
|
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import { has } from 'lodash';
|
import { has } from 'lodash';
|
||||||
import { selectDefaultWidths, selectUserSectionWidths, useSelector } from '../store/redux';
|
import {
|
||||||
|
selectDefaultWidths,
|
||||||
|
selectPushVsOverlay,
|
||||||
|
selectUserSectionWidths,
|
||||||
|
useSelector,
|
||||||
|
} from '../store/redux';
|
||||||
import {
|
import {
|
||||||
PREVIEW_SECTION_BACK_BUTTON_TEST_ID,
|
PREVIEW_SECTION_BACK_BUTTON_TEST_ID,
|
||||||
PREVIEW_SECTION_CLOSE_BUTTON_TEST_ID,
|
PREVIEW_SECTION_CLOSE_BUTTON_TEST_ID,
|
||||||
|
@ -85,14 +90,17 @@ export const PreviewSection: React.FC<PreviewSectionProps> = memo(
|
||||||
|
|
||||||
const { rightPercentage } = useSelector(selectUserSectionWidths);
|
const { rightPercentage } = useSelector(selectUserSectionWidths);
|
||||||
const defaultPercentages = useSelector(selectDefaultWidths);
|
const defaultPercentages = useSelector(selectDefaultWidths);
|
||||||
|
const type = useSelector(selectPushVsOverlay);
|
||||||
|
|
||||||
// Calculate the width of the preview section based on the following
|
// Calculate the width of the preview section based on the following
|
||||||
// - if only the right section is visible, then we use 100% of the width (minus some padding)
|
// - if only the right section is visible, then we use 100% of the width (minus some padding)
|
||||||
// - if both the right and left sections are visible, we use the width of the right section (minus the same padding)
|
// - if both the right and left sections are visible, we use the width of the right section (minus the same padding)
|
||||||
const width = useMemo(() => {
|
const width = useMemo(() => {
|
||||||
const percentage = rightPercentage ? rightPercentage : defaultPercentages.rightPercentage;
|
const percentage = rightPercentage
|
||||||
|
? rightPercentage
|
||||||
|
: defaultPercentages[type].rightPercentage;
|
||||||
return showExpanded ? `calc(${percentage}% - 8px)` : `calc(100% - 8px)`;
|
return showExpanded ? `calc(${percentage}% - 8px)` : `calc(100% - 8px)`;
|
||||||
}, [defaultPercentages.rightPercentage, rightPercentage, showExpanded]);
|
}, [defaultPercentages, rightPercentage, showExpanded, type]);
|
||||||
|
|
||||||
const closeButton = (
|
const closeButton = (
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { css } from '@emotion/react';
|
||||||
import { changeUserSectionWidthsAction } from '../store/actions';
|
import { changeUserSectionWidthsAction } from '../store/actions';
|
||||||
import {
|
import {
|
||||||
selectDefaultWidths,
|
selectDefaultWidths,
|
||||||
|
selectPushVsOverlay,
|
||||||
selectUserSectionWidths,
|
selectUserSectionWidths,
|
||||||
useDispatch,
|
useDispatch,
|
||||||
useSelector,
|
useSelector,
|
||||||
|
@ -52,15 +53,16 @@ export const ResizableContainer: React.FC<ResizableContainerProps> = memo(
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const { leftPercentage, rightPercentage } = useSelector(selectUserSectionWidths);
|
const { leftPercentage, rightPercentage } = useSelector(selectUserSectionWidths);
|
||||||
|
const type = useSelector(selectPushVsOverlay);
|
||||||
const defaultPercentages = useSelector(selectDefaultWidths);
|
const defaultPercentages = useSelector(selectDefaultWidths);
|
||||||
|
|
||||||
const initialLeftPercentage = useMemo(
|
const initialLeftPercentage = useMemo(
|
||||||
() => leftPercentage || defaultPercentages.leftPercentage,
|
() => leftPercentage || defaultPercentages[type].leftPercentage,
|
||||||
[defaultPercentages.leftPercentage, leftPercentage]
|
[defaultPercentages, leftPercentage, type]
|
||||||
);
|
);
|
||||||
const initialRightPercentage = useMemo(
|
const initialRightPercentage = useMemo(
|
||||||
() => rightPercentage || defaultPercentages.rightPercentage,
|
() => rightPercentage || defaultPercentages[type].rightPercentage,
|
||||||
[defaultPercentages.rightPercentage, rightPercentage]
|
[defaultPercentages, rightPercentage, type]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onWidthChange = useCallback(
|
const onWidthChange = useCallback(
|
||||||
|
|
|
@ -6,14 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import {
|
import { useWindowWidth } from './use_window_width';
|
||||||
FULL_WIDTH_PADDING,
|
|
||||||
MAX_RESOLUTION_BREAKPOINT,
|
|
||||||
MIN_RESOLUTION_BREAKPOINT,
|
|
||||||
RIGHT_SECTION_MAX_WIDTH,
|
|
||||||
RIGHT_SECTION_MIN_WIDTH,
|
|
||||||
useWindowWidth,
|
|
||||||
} from './use_window_width';
|
|
||||||
import { useDispatch } from '../store/redux';
|
import { useDispatch } from '../store/redux';
|
||||||
import { setDefaultWidthsAction } from '../store/actions';
|
import { setDefaultWidthsAction } from '../store/actions';
|
||||||
|
|
||||||
|
@ -48,7 +41,7 @@ describe('useWindowWidth', () => {
|
||||||
expect(mockUseDispatch).not.toHaveBeenCalled();
|
expect(mockUseDispatch).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle very small screens', () => {
|
it('should handle screens below 380px', () => {
|
||||||
global.innerWidth = 300;
|
global.innerWidth = 300;
|
||||||
|
|
||||||
const mockUseDispatch = jest.fn();
|
const mockUseDispatch = jest.fn();
|
||||||
|
@ -59,14 +52,17 @@ describe('useWindowWidth', () => {
|
||||||
expect(hookResult.result.current).toEqual(300);
|
expect(hookResult.result.current).toEqual(300);
|
||||||
expect(mockUseDispatch).toHaveBeenCalledWith(
|
expect(mockUseDispatch).toHaveBeenCalledWith(
|
||||||
setDefaultWidthsAction({
|
setDefaultWidthsAction({
|
||||||
left: -48,
|
leftOverlay: -48,
|
||||||
right: 300,
|
leftPush: 380,
|
||||||
preview: 300,
|
previewOverlay: 300,
|
||||||
|
previewPush: 300,
|
||||||
|
rightOverlay: 300,
|
||||||
|
rightPush: 300,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle small screens', () => {
|
it('should handle screens between 380px and 992px', () => {
|
||||||
global.innerWidth = 500;
|
global.innerWidth = 500;
|
||||||
|
|
||||||
const mockUseDispatch = jest.fn();
|
const mockUseDispatch = jest.fn();
|
||||||
|
@ -77,58 +73,99 @@ describe('useWindowWidth', () => {
|
||||||
expect(hookResult.result.current).toEqual(500);
|
expect(hookResult.result.current).toEqual(500);
|
||||||
expect(mockUseDispatch).toHaveBeenCalledWith(
|
expect(mockUseDispatch).toHaveBeenCalledWith(
|
||||||
setDefaultWidthsAction({
|
setDefaultWidthsAction({
|
||||||
left: 72,
|
leftOverlay: 72,
|
||||||
right: 380,
|
leftPush: 380,
|
||||||
preview: 380,
|
previewOverlay: 380,
|
||||||
|
previewPush: 380,
|
||||||
|
rightOverlay: 380,
|
||||||
|
rightPush: 380,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle medium screens', () => {
|
it('should handle screens between 992px and 1600px', () => {
|
||||||
global.innerWidth = 1300;
|
global.innerWidth = 1000;
|
||||||
|
|
||||||
const mockUseDispatch = jest.fn();
|
const mockUseDispatch = jest.fn();
|
||||||
(useDispatch as jest.Mock).mockImplementation(() => mockUseDispatch);
|
(useDispatch as jest.Mock).mockImplementation(() => mockUseDispatch);
|
||||||
|
|
||||||
const hookResult = renderHook(() => useWindowWidth());
|
const hookResult = renderHook(() => useWindowWidth());
|
||||||
|
|
||||||
const right =
|
const rightOverlay = 380 + (750 - 380) * ((1000 - 992) / (1920 - 992));
|
||||||
RIGHT_SECTION_MIN_WIDTH +
|
const leftOverlay = 1000 - rightOverlay - 48;
|
||||||
(RIGHT_SECTION_MAX_WIDTH - RIGHT_SECTION_MIN_WIDTH) *
|
const previewOverlay = rightOverlay;
|
||||||
((1300 - MIN_RESOLUTION_BREAKPOINT) /
|
const rightPush = 380 + (600 - 380) * ((1000 - 1600) / (2560 - 1600));
|
||||||
(MAX_RESOLUTION_BREAKPOINT - MIN_RESOLUTION_BREAKPOINT));
|
const leftPush = 380;
|
||||||
const left = 1300 - right - FULL_WIDTH_PADDING;
|
const previewPush = rightPush;
|
||||||
const preview = right;
|
|
||||||
|
|
||||||
expect(hookResult.result.current).toEqual(1300);
|
expect(hookResult.result.current).toEqual(1000);
|
||||||
expect(mockUseDispatch).toHaveBeenCalledWith(
|
expect(mockUseDispatch).toHaveBeenCalledWith(
|
||||||
setDefaultWidthsAction({
|
setDefaultWidthsAction({
|
||||||
left,
|
rightOverlay,
|
||||||
right,
|
leftOverlay,
|
||||||
preview,
|
previewOverlay,
|
||||||
|
leftPush,
|
||||||
|
previewPush,
|
||||||
|
rightPush,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle large screens', () => {
|
it('should handle screens between 1600px and 1920', () => {
|
||||||
global.innerWidth = 2500;
|
global.innerWidth = 1800;
|
||||||
|
|
||||||
const mockUseDispatch = jest.fn();
|
const mockUseDispatch = jest.fn();
|
||||||
(useDispatch as jest.Mock).mockImplementation(() => mockUseDispatch);
|
(useDispatch as jest.Mock).mockImplementation(() => mockUseDispatch);
|
||||||
|
|
||||||
const hookResult = renderHook(() => useWindowWidth());
|
const hookResult = renderHook(() => useWindowWidth());
|
||||||
|
|
||||||
expect(hookResult.result.current).toEqual(2500);
|
const rightOverlay = 380 + (750 - 380) * ((1800 - 992) / (1920 - 992));
|
||||||
|
const leftOverlay = ((1800 - rightOverlay) * 80) / 100;
|
||||||
|
const previewOverlay = rightOverlay;
|
||||||
|
const rightPush = 380 + (600 - 380) * ((1800 - 1600) / (2560 - 1600));
|
||||||
|
const leftPush = ((1800 - rightPush - 200) * 40) / 100;
|
||||||
|
const previewPush = rightPush;
|
||||||
|
|
||||||
|
expect(hookResult.result.current).toEqual(1800);
|
||||||
expect(mockUseDispatch).toHaveBeenCalledWith(
|
expect(mockUseDispatch).toHaveBeenCalledWith(
|
||||||
setDefaultWidthsAction({
|
setDefaultWidthsAction({
|
||||||
left: 1400,
|
rightOverlay,
|
||||||
right: 750,
|
leftOverlay,
|
||||||
preview: 750,
|
previewOverlay,
|
||||||
|
leftPush,
|
||||||
|
previewPush,
|
||||||
|
rightPush,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle very large screens', () => {
|
it('should handle screens between 1920px and 2560px', () => {
|
||||||
|
global.innerWidth = 2400;
|
||||||
|
|
||||||
|
const mockUseDispatch = jest.fn();
|
||||||
|
(useDispatch as jest.Mock).mockImplementation(() => mockUseDispatch);
|
||||||
|
|
||||||
|
const hookResult = renderHook(() => useWindowWidth());
|
||||||
|
|
||||||
|
const leftOverlay = ((2400 - 750) * 80) / 100;
|
||||||
|
const rightPush = 380 + (600 - 380) * ((2400 - 1600) / (2560 - 1600));
|
||||||
|
const leftPush = ((2400 - rightPush - 200) * 40) / 100;
|
||||||
|
const previewPush = rightPush;
|
||||||
|
|
||||||
|
expect(hookResult.result.current).toEqual(2400);
|
||||||
|
expect(mockUseDispatch).toHaveBeenCalledWith(
|
||||||
|
setDefaultWidthsAction({
|
||||||
|
rightOverlay: 750,
|
||||||
|
leftOverlay,
|
||||||
|
previewOverlay: 750,
|
||||||
|
leftPush,
|
||||||
|
previewPush,
|
||||||
|
rightPush,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle screens above 2560px', () => {
|
||||||
global.innerWidth = 3800;
|
global.innerWidth = 3800;
|
||||||
|
|
||||||
const mockUseDispatch = jest.fn();
|
const mockUseDispatch = jest.fn();
|
||||||
|
@ -139,9 +176,12 @@ describe('useWindowWidth', () => {
|
||||||
expect(hookResult.result.current).toEqual(3800);
|
expect(hookResult.result.current).toEqual(3800);
|
||||||
expect(mockUseDispatch).toHaveBeenCalledWith(
|
expect(mockUseDispatch).toHaveBeenCalledWith(
|
||||||
setDefaultWidthsAction({
|
setDefaultWidthsAction({
|
||||||
left: 1500,
|
leftOverlay: 1500,
|
||||||
right: 750,
|
leftPush: 1200,
|
||||||
preview: 750,
|
previewOverlay: 750,
|
||||||
|
previewPush: 600,
|
||||||
|
rightOverlay: 750,
|
||||||
|
rightPush: 600,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,18 +9,143 @@ import { useLayoutEffect, useState } from 'react';
|
||||||
import { useDispatch } from '../store/redux';
|
import { useDispatch } from '../store/redux';
|
||||||
import { setDefaultWidthsAction } from '../store/actions';
|
import { setDefaultWidthsAction } from '../store/actions';
|
||||||
|
|
||||||
export const RIGHT_SECTION_MIN_WIDTH = 380;
|
const RESOLUTION_BREAKPOINTS = {
|
||||||
export const MIN_RESOLUTION_BREAKPOINT = 992;
|
RIGHT: {
|
||||||
export const RIGHT_SECTION_MAX_WIDTH = 750;
|
MIN: 992, // resolution below which the width is fixed to 380px
|
||||||
export const MAX_RESOLUTION_BREAKPOINT = 1920;
|
OVERLAY_MAX: 1920, // resolution above which the overlay width is fixed to 750px
|
||||||
|
PUSH_MIN: 1600, // resolution below which the push width is fixed to 380px
|
||||||
|
PUSH_MAX: 2560, // resolution above which the push width is fixed to 600px
|
||||||
|
},
|
||||||
|
LEFT: {
|
||||||
|
MIN: 1600, // resolution below which the overlay width goes full width (minus the padding) and the push width goes to its fixed 380px
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const FULL_WIDTH_PADDING = 48;
|
||||||
|
const NAVIGATION_WIDTH = 200;
|
||||||
|
|
||||||
const LEFT_SECTION_MAX_WIDTH = 1500;
|
const SECTION_WIDTHS = {
|
||||||
|
RIGHT: {
|
||||||
const FULL_WIDTH_BREAKPOINT = 1600;
|
MIN: 380,
|
||||||
export const FULL_WIDTH_PADDING = 48;
|
OVERLAY_MAX: 750,
|
||||||
|
PUSH_MAX: 600,
|
||||||
|
},
|
||||||
|
LEFT: {
|
||||||
|
OVERLAY_MAX: 1500,
|
||||||
|
PUSH_MIN: 380,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook that returns the browser window width
|
* Calculates the default widths for the right section of the expandable flyout in push and overlay modes.
|
||||||
|
*
|
||||||
|
* For overlay mode, the flyout right section scales as follows:
|
||||||
|
* - for window widths below 380px, we make sure that the width is identical to the window width
|
||||||
|
* - for window widths between 380px and 992px, the width is fixed at 380px
|
||||||
|
* - for window widths between 992px and 1920px, the width scales linearly between 380px and 750px
|
||||||
|
* - for window widths above 1920px, the width is fixed at 750px
|
||||||
|
*
|
||||||
|
* For push mode, the flyout right section scales as follows:
|
||||||
|
* - for window widths below 380px, we make sure that the flyout width is identical to the window width (also, EUI actually automatically switches the flyout to overlay mode)
|
||||||
|
* - for window widths between 380px and 1600px, the width is fixed at 380px
|
||||||
|
* - for window widths between 1600x and 2560px, the width scales linearly between 380px and 600px
|
||||||
|
* - for window widths above 2560px, the width is fixed at 600px
|
||||||
|
*/
|
||||||
|
const calculateRightSectionDefaultWidths = (
|
||||||
|
windowWidth: number
|
||||||
|
): {
|
||||||
|
overlay: number;
|
||||||
|
push: number;
|
||||||
|
} => {
|
||||||
|
// for tiny window widths (less than 380px), we want to make sure the flyout will not go outside the window width
|
||||||
|
if (windowWidth < SECTION_WIDTHS.RIGHT.MIN) {
|
||||||
|
return {
|
||||||
|
overlay: windowWidth,
|
||||||
|
push: windowWidth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// for window widths between 380px and 992px, the width is fixed to the 380px
|
||||||
|
// EUI automatically switches a push flyout to overlay below 992px, so the push value here is actually a bit unnecessary but we return it anyway to ensure the redux store is always populated with a value
|
||||||
|
if (windowWidth < RESOLUTION_BREAKPOINTS.RIGHT.MIN) {
|
||||||
|
return {
|
||||||
|
overlay: SECTION_WIDTHS.RIGHT.MIN,
|
||||||
|
push: SECTION_WIDTHS.RIGHT.MIN,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// in overlay mode, the width will linearly scale from 380px (at 992px resolution) to 750px (at 1920px resolution)
|
||||||
|
const ratioWidthOverlayMode =
|
||||||
|
(SECTION_WIDTHS.RIGHT.OVERLAY_MAX - SECTION_WIDTHS.RIGHT.MIN) *
|
||||||
|
((windowWidth - RESOLUTION_BREAKPOINTS.RIGHT.MIN) /
|
||||||
|
(RESOLUTION_BREAKPOINTS.RIGHT.OVERLAY_MAX - RESOLUTION_BREAKPOINTS.RIGHT.MIN));
|
||||||
|
// this will ensure that in push in mode the width will never go bigger than 750px in higher resolutions
|
||||||
|
const overlayWidth = Math.min(
|
||||||
|
SECTION_WIDTHS.RIGHT.MIN + ratioWidthOverlayMode,
|
||||||
|
SECTION_WIDTHS.RIGHT.OVERLAY_MAX
|
||||||
|
);
|
||||||
|
|
||||||
|
// in push mode, the width will linearly scale from 380px (at 1600px resolution) to 600px (at 2560px resolution)
|
||||||
|
const ratioWidthPushMode =
|
||||||
|
(SECTION_WIDTHS.RIGHT.PUSH_MAX - SECTION_WIDTHS.RIGHT.MIN) *
|
||||||
|
((windowWidth - RESOLUTION_BREAKPOINTS.RIGHT.PUSH_MIN) /
|
||||||
|
(RESOLUTION_BREAKPOINTS.RIGHT.PUSH_MAX - RESOLUTION_BREAKPOINTS.RIGHT.PUSH_MIN));
|
||||||
|
// this will ensure that in push mode the width will never go bigger than 600px in higher resolutions
|
||||||
|
const pushWidth = Math.min(
|
||||||
|
SECTION_WIDTHS.RIGHT.MIN + ratioWidthPushMode,
|
||||||
|
SECTION_WIDTHS.RIGHT.PUSH_MAX
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
overlay: overlayWidth,
|
||||||
|
push: pushWidth,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the default widths for the left section of the expandable flyout in push and overlay modes.
|
||||||
|
*
|
||||||
|
* For overlay mode, the flyout left section scales as follows:
|
||||||
|
* - for window widths below 1600px, the width is taking the full screen minus a 48px padding
|
||||||
|
* - for window widths above 1600px, the width corresponds to 80% of the remaining space
|
||||||
|
*
|
||||||
|
* For push mode, the flyout left section scales as follows:
|
||||||
|
* - for window widths below 1600px, the width is fixed at 380px
|
||||||
|
* - for window widths above 1600px, the width corresponds to 40% of the remaining space
|
||||||
|
*/
|
||||||
|
const calculateLeftSectionDefaultWidths = (
|
||||||
|
windowWidth: number,
|
||||||
|
rightSectionWidthOverlay: number,
|
||||||
|
rightSectionWidthPush: number
|
||||||
|
): {
|
||||||
|
overlay: number;
|
||||||
|
push: number;
|
||||||
|
} => {
|
||||||
|
// for window widths below 1600px, the overlay width will use the remaining space (minus a small padding)
|
||||||
|
// for window widths above 1600px, the overlay width will use 80% of the remaining space, while never going bigger than 1500px
|
||||||
|
const overlayWidth =
|
||||||
|
windowWidth <= RESOLUTION_BREAKPOINTS.LEFT.MIN
|
||||||
|
? windowWidth - rightSectionWidthOverlay - FULL_WIDTH_PADDING
|
||||||
|
: Math.min(
|
||||||
|
((windowWidth - rightSectionWidthOverlay) * 80) / 100,
|
||||||
|
SECTION_WIDTHS.LEFT.OVERLAY_MAX
|
||||||
|
);
|
||||||
|
|
||||||
|
// for window widths below 1600px, the push width will be fixed to 380px
|
||||||
|
// for window widths above 1600px, the push width will use 40% of the remaining space (excluding the navigation width)
|
||||||
|
const pushWidth =
|
||||||
|
windowWidth <= RESOLUTION_BREAKPOINTS.LEFT.MIN
|
||||||
|
? SECTION_WIDTHS.LEFT.PUSH_MIN
|
||||||
|
: ((windowWidth - rightSectionWidthPush - NAVIGATION_WIDTH) * 40) / 100;
|
||||||
|
|
||||||
|
return {
|
||||||
|
overlay: overlayWidth,
|
||||||
|
push: pushWidth,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook that returns the browser window width.
|
||||||
|
* It also calculates all the default widths values for the flyout to render in overlay and push modes then stores them in Redux.
|
||||||
*/
|
*/
|
||||||
export const useWindowWidth = (): number => {
|
export const useWindowWidth = (): number => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
@ -32,47 +157,35 @@ export const useWindowWidth = (): number => {
|
||||||
setWidth(window.innerWidth);
|
setWidth(window.innerWidth);
|
||||||
|
|
||||||
const windowWidth = window.innerWidth;
|
const windowWidth = window.innerWidth;
|
||||||
|
|
||||||
|
// if the browser's window width is 0 (which should only happen the very first time this hook is called) there is no point in calculating all the default flyout's widths
|
||||||
if (windowWidth !== 0) {
|
if (windowWidth !== 0) {
|
||||||
let rightSectionWidth: number;
|
const { overlay: rightSectionWidthOverlay, push: rightSectionWidthPush } =
|
||||||
if (windowWidth < MIN_RESOLUTION_BREAKPOINT) {
|
calculateRightSectionDefaultWidths(windowWidth);
|
||||||
// the right section's width will grow from 380px (at 992px resolution) while handling tiny screens by not going smaller than the window width
|
|
||||||
rightSectionWidth = Math.min(RIGHT_SECTION_MIN_WIDTH, windowWidth);
|
|
||||||
} else {
|
|
||||||
const ratioWidth =
|
|
||||||
(RIGHT_SECTION_MAX_WIDTH - RIGHT_SECTION_MIN_WIDTH) *
|
|
||||||
((windowWidth - MIN_RESOLUTION_BREAKPOINT) /
|
|
||||||
(MAX_RESOLUTION_BREAKPOINT - MIN_RESOLUTION_BREAKPOINT));
|
|
||||||
|
|
||||||
// the right section's width will grow to 750px (at 1920px resolution) and will never go bigger than 750px in higher resolutions
|
const { overlay: leftSectionWidthOverlay, push: leftSectionWidthPush } =
|
||||||
rightSectionWidth = Math.min(
|
calculateLeftSectionDefaultWidths(
|
||||||
RIGHT_SECTION_MIN_WIDTH + ratioWidth,
|
windowWidth,
|
||||||
RIGHT_SECTION_MAX_WIDTH
|
rightSectionWidthOverlay,
|
||||||
|
rightSectionWidthPush
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let leftSectionWidth: number;
|
const previewSectionWidthOverlay: number = rightSectionWidthOverlay;
|
||||||
// the left section's width will be nearly the remaining space for resolution lower than 1600px
|
const previewSectionWidthPush: number = rightSectionWidthPush;
|
||||||
if (windowWidth <= FULL_WIDTH_BREAKPOINT) {
|
|
||||||
leftSectionWidth = windowWidth - rightSectionWidth - FULL_WIDTH_PADDING;
|
|
||||||
} else {
|
|
||||||
// the left section's width will be taking 80% of the remaining space for resolution higher than 1600px, while never going bigger than 1500px
|
|
||||||
leftSectionWidth = Math.min(
|
|
||||||
((windowWidth - rightSectionWidth) * 80) / 100,
|
|
||||||
LEFT_SECTION_MAX_WIDTH
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const previewSectionWidth: number = rightSectionWidth;
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setDefaultWidthsAction({
|
setDefaultWidthsAction({
|
||||||
right: rightSectionWidth,
|
rightOverlay: rightSectionWidthOverlay,
|
||||||
left: leftSectionWidth,
|
leftOverlay: leftSectionWidthOverlay,
|
||||||
preview: previewSectionWidth,
|
previewOverlay: previewSectionWidthOverlay,
|
||||||
|
rightPush: rightSectionWidthPush,
|
||||||
|
leftPush: leftSectionWidthPush,
|
||||||
|
previewPush: previewSectionWidthPush,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', updateSize);
|
window.addEventListener('resize', updateSize);
|
||||||
updateSize();
|
updateSize();
|
||||||
return () => window.removeEventListener('resize', updateSize);
|
return () => window.removeEventListener('resize', updateSize);
|
||||||
|
|
|
@ -144,17 +144,29 @@ export const changePushVsOverlayAction = createAction<{
|
||||||
|
|
||||||
export const setDefaultWidthsAction = createAction<{
|
export const setDefaultWidthsAction = createAction<{
|
||||||
/**
|
/**
|
||||||
* Default width for the right section
|
* Default width for the right section in overlay mode
|
||||||
*/
|
*/
|
||||||
right: number;
|
rightOverlay: number;
|
||||||
/**
|
/**
|
||||||
* Default width for the left section
|
* Default width for the left section in overlay mode
|
||||||
*/
|
*/
|
||||||
left: number;
|
leftOverlay: number;
|
||||||
/**
|
/**
|
||||||
* Default width for the preview section
|
* Default width for the preview section in overlay mode
|
||||||
*/
|
*/
|
||||||
preview: number;
|
previewOverlay: number;
|
||||||
|
/**
|
||||||
|
* Default width for the right section in push mode
|
||||||
|
*/
|
||||||
|
rightPush: number;
|
||||||
|
/**
|
||||||
|
* Default width for the left section in push mode
|
||||||
|
*/
|
||||||
|
leftPush: number;
|
||||||
|
/**
|
||||||
|
* Default width for the preview section in push mode
|
||||||
|
*/
|
||||||
|
previewPush: number;
|
||||||
}>(ActionType.setDefaultWidths);
|
}>(ActionType.setDefaultWidths);
|
||||||
|
|
||||||
export const changeUserCollapsedWidthAction = createAction<{
|
export const changeUserCollapsedWidthAction = createAction<{
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import { FlyoutPanelProps } from '../types';
|
import { FlyoutPanelProps } from '../types';
|
||||||
import { panelsReducer, uiReducer } from './reducers';
|
import { panelsReducer, uiReducer } from './reducers';
|
||||||
import { initialPanelsState, PanelsState, initialUiState, UiState } from './state';
|
import { initialPanelsState, initialUiState, PanelsState, UiState } from './state';
|
||||||
import {
|
import {
|
||||||
changePushVsOverlayAction,
|
changePushVsOverlayAction,
|
||||||
changeUserCollapsedWidthAction,
|
changeUserCollapsedWidthAction,
|
||||||
|
@ -874,21 +874,34 @@ describe('uiReducer', () => {
|
||||||
it('should set value state is empty', () => {
|
it('should set value state is empty', () => {
|
||||||
const state: UiState = initialUiState;
|
const state: UiState = initialUiState;
|
||||||
const action = setDefaultWidthsAction({
|
const action = setDefaultWidthsAction({
|
||||||
right: 200,
|
rightOverlay: 300,
|
||||||
left: 600,
|
leftOverlay: 900,
|
||||||
preview: 200,
|
previewOverlay: 300,
|
||||||
|
rightPush: 200,
|
||||||
|
leftPush: 600,
|
||||||
|
previewPush: 200,
|
||||||
});
|
});
|
||||||
const newState: UiState = uiReducer(state, action);
|
const newState: UiState = uiReducer(state, action);
|
||||||
|
|
||||||
expect(newState).toEqual({
|
expect(newState).toEqual({
|
||||||
...state,
|
...state,
|
||||||
defaultWidths: {
|
defaultWidths: {
|
||||||
rightWidth: 200,
|
overlay: {
|
||||||
leftWidth: 600,
|
rightWidth: 300,
|
||||||
previewWidth: 200,
|
leftWidth: 900,
|
||||||
rightPercentage: 25,
|
previewWidth: 300,
|
||||||
leftPercentage: 75,
|
rightPercentage: 25,
|
||||||
previewPercentage: 25,
|
leftPercentage: 75,
|
||||||
|
previewPercentage: 25,
|
||||||
|
},
|
||||||
|
push: {
|
||||||
|
rightWidth: 200,
|
||||||
|
leftWidth: 600,
|
||||||
|
previewWidth: 200,
|
||||||
|
rightPercentage: 25,
|
||||||
|
leftPercentage: 75,
|
||||||
|
previewPercentage: 25,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -897,30 +910,53 @@ describe('uiReducer', () => {
|
||||||
const state: UiState = {
|
const state: UiState = {
|
||||||
...initialUiState,
|
...initialUiState,
|
||||||
defaultWidths: {
|
defaultWidths: {
|
||||||
rightWidth: 200,
|
overlay: {
|
||||||
leftWidth: 600,
|
rightWidth: 300,
|
||||||
previewWidth: 200,
|
leftWidth: 900,
|
||||||
rightPercentage: 25,
|
previewWidth: 300,
|
||||||
leftPercentage: 75,
|
rightPercentage: 25,
|
||||||
previewPercentage: 25,
|
leftPercentage: 75,
|
||||||
|
previewPercentage: 25,
|
||||||
|
},
|
||||||
|
push: {
|
||||||
|
rightWidth: 200,
|
||||||
|
leftWidth: 600,
|
||||||
|
previewWidth: 200,
|
||||||
|
rightPercentage: 25,
|
||||||
|
leftPercentage: 75,
|
||||||
|
previewPercentage: 25,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const action = setDefaultWidthsAction({
|
const action = setDefaultWidthsAction({
|
||||||
right: 500,
|
rightOverlay: 500,
|
||||||
left: 500,
|
leftOverlay: 500,
|
||||||
preview: 500,
|
previewOverlay: 500,
|
||||||
|
rightPush: 500,
|
||||||
|
leftPush: 500,
|
||||||
|
previewPush: 500,
|
||||||
});
|
});
|
||||||
const newState: UiState = uiReducer(state, action);
|
const newState: UiState = uiReducer(state, action);
|
||||||
|
|
||||||
expect(newState).toEqual({
|
expect(newState).toEqual({
|
||||||
...state,
|
...state,
|
||||||
defaultWidths: {
|
defaultWidths: {
|
||||||
rightWidth: 500,
|
overlay: {
|
||||||
leftWidth: 500,
|
rightWidth: 500,
|
||||||
previewWidth: 500,
|
leftWidth: 500,
|
||||||
rightPercentage: 50,
|
previewWidth: 500,
|
||||||
leftPercentage: 50,
|
rightPercentage: 50,
|
||||||
previewPercentage: 50,
|
leftPercentage: 50,
|
||||||
|
previewPercentage: 50,
|
||||||
|
},
|
||||||
|
push: {
|
||||||
|
rightWidth: 500,
|
||||||
|
leftWidth: 500,
|
||||||
|
previewWidth: 500,
|
||||||
|
rightPercentage: 50,
|
||||||
|
leftPercentage: 50,
|
||||||
|
previewPercentage: 50,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,22 +8,22 @@
|
||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
import deepEqual from 'react-fast-compare';
|
import deepEqual from 'react-fast-compare';
|
||||||
import {
|
import {
|
||||||
openPanelsAction,
|
|
||||||
openLeftPanelAction,
|
|
||||||
openRightPanelAction,
|
|
||||||
closePanelsAction,
|
|
||||||
closeLeftPanelAction,
|
|
||||||
closePreviewPanelAction,
|
|
||||||
closeRightPanelAction,
|
|
||||||
previousPreviewPanelAction,
|
|
||||||
openPreviewPanelAction,
|
|
||||||
urlChangedAction,
|
|
||||||
changePushVsOverlayAction,
|
changePushVsOverlayAction,
|
||||||
setDefaultWidthsAction,
|
|
||||||
changeUserCollapsedWidthAction,
|
changeUserCollapsedWidthAction,
|
||||||
changeUserExpandedWidthAction,
|
changeUserExpandedWidthAction,
|
||||||
changeUserSectionWidthsAction,
|
changeUserSectionWidthsAction,
|
||||||
|
closeLeftPanelAction,
|
||||||
|
closePanelsAction,
|
||||||
|
closePreviewPanelAction,
|
||||||
|
closeRightPanelAction,
|
||||||
|
openLeftPanelAction,
|
||||||
|
openPanelsAction,
|
||||||
|
openPreviewPanelAction,
|
||||||
|
openRightPanelAction,
|
||||||
|
previousPreviewPanelAction,
|
||||||
resetAllUserChangedWidthsAction,
|
resetAllUserChangedWidthsAction,
|
||||||
|
setDefaultWidthsAction,
|
||||||
|
urlChangedAction,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { initialPanelsState, initialUiState } from './state';
|
import { initialPanelsState, initialUiState } from './state';
|
||||||
|
|
||||||
|
@ -167,14 +167,30 @@ export const uiReducer = createReducer(initialUiState, (builder) => {
|
||||||
state.pushVsOverlay = type;
|
state.pushVsOverlay = type;
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(setDefaultWidthsAction, (state, { payload: { right, left, preview } }) => {
|
builder.addCase(
|
||||||
state.defaultWidths.rightWidth = right;
|
setDefaultWidthsAction,
|
||||||
state.defaultWidths.leftWidth = left;
|
(
|
||||||
state.defaultWidths.previewWidth = preview;
|
state,
|
||||||
state.defaultWidths.rightPercentage = (right / (right + left)) * 100;
|
{ payload: { rightOverlay, leftOverlay, previewOverlay, rightPush, leftPush, previewPush } }
|
||||||
state.defaultWidths.leftPercentage = (left / (right + left)) * 100;
|
) => {
|
||||||
state.defaultWidths.previewPercentage = (right / (right + left)) * 100;
|
state.defaultWidths.overlay.rightWidth = rightOverlay;
|
||||||
});
|
state.defaultWidths.overlay.leftWidth = leftOverlay;
|
||||||
|
state.defaultWidths.overlay.previewWidth = previewOverlay;
|
||||||
|
state.defaultWidths.overlay.rightPercentage =
|
||||||
|
(rightOverlay / (rightOverlay + leftOverlay)) * 100;
|
||||||
|
state.defaultWidths.overlay.leftPercentage =
|
||||||
|
(leftOverlay / (rightOverlay + leftOverlay)) * 100;
|
||||||
|
state.defaultWidths.overlay.previewPercentage =
|
||||||
|
(previewOverlay / (previewOverlay + leftOverlay)) * 100;
|
||||||
|
|
||||||
|
state.defaultWidths.push.rightWidth = rightPush;
|
||||||
|
state.defaultWidths.push.leftWidth = leftPush;
|
||||||
|
state.defaultWidths.push.previewWidth = previewPush;
|
||||||
|
state.defaultWidths.push.rightPercentage = (rightPush / (rightPush + leftPush)) * 100;
|
||||||
|
state.defaultWidths.push.leftPercentage = (leftPush / (rightPush + leftPush)) * 100;
|
||||||
|
state.defaultWidths.push.previewPercentage = (previewPush / (previewPush + leftPush)) * 100;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
builder.addCase(changeUserCollapsedWidthAction, (state, { payload: { width } }) => {
|
builder.addCase(changeUserCollapsedWidthAction, (state, { payload: { width } }) => {
|
||||||
state.userFlyoutWidths.collapsedWidth = width;
|
state.userFlyoutWidths.collapsedWidth = width;
|
||||||
|
|
|
@ -12,10 +12,10 @@ import { createSelector } from 'reselect';
|
||||||
import { panelsReducer, uiReducer } from './reducers';
|
import { panelsReducer, uiReducer } from './reducers';
|
||||||
import { initialState, State } from './state';
|
import { initialState, State } from './state';
|
||||||
import {
|
import {
|
||||||
savePushVsOverlayToLocalStorageMiddleware,
|
|
||||||
saveUserSectionWidthsToLocalStorageMiddleware,
|
|
||||||
saveUserFlyoutWidthsToLocalStorageMiddleware,
|
|
||||||
clearAllUserWidthsFromLocalStorageMiddleware,
|
clearAllUserWidthsFromLocalStorageMiddleware,
|
||||||
|
savePushVsOverlayToLocalStorageMiddleware,
|
||||||
|
saveUserFlyoutWidthsToLocalStorageMiddleware,
|
||||||
|
saveUserSectionWidthsToLocalStorageMiddleware,
|
||||||
} from './middlewares';
|
} from './middlewares';
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
|
|
|
@ -46,7 +46,7 @@ export const initialPanelsState: PanelsState = {
|
||||||
needsSync: false,
|
needsSync: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface DefaultWidthsState {
|
interface DefaultSectionWidthsState {
|
||||||
/**
|
/**
|
||||||
* Default width for the right section (calculated from the window width)
|
* Default width for the right section (calculated from the window width)
|
||||||
*/
|
*/
|
||||||
|
@ -73,6 +73,17 @@ export interface DefaultWidthsState {
|
||||||
previewPercentage: number;
|
previewPercentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DefaultWidthsState {
|
||||||
|
/**
|
||||||
|
* Default width for all the sections in overlay mode
|
||||||
|
*/
|
||||||
|
overlay: DefaultSectionWidthsState;
|
||||||
|
/**
|
||||||
|
* Default widths for all the sections in push mode
|
||||||
|
*/
|
||||||
|
push: DefaultSectionWidthsState;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserFlyoutWidthsState {
|
export interface UserFlyoutWidthsState {
|
||||||
/**
|
/**
|
||||||
* Width of the collapsed flyout
|
* Width of the collapsed flyout
|
||||||
|
@ -116,7 +127,10 @@ export interface UiState {
|
||||||
|
|
||||||
export const initialUiState: UiState = {
|
export const initialUiState: UiState = {
|
||||||
pushVsOverlay: 'overlay',
|
pushVsOverlay: 'overlay',
|
||||||
defaultWidths: {} as DefaultWidthsState,
|
defaultWidths: {
|
||||||
|
overlay: {},
|
||||||
|
push: {},
|
||||||
|
} as DefaultWidthsState,
|
||||||
userFlyoutWidths: {},
|
userFlyoutWidths: {},
|
||||||
userSectionWidths: {} as UserSectionWidthsState,
|
userSectionWidths: {} as UserSectionWidthsState,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue