@atlaskit/page-layout 1.6.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/__perf__/utils/product-integration/sample-footer.tsx +2 -2
  3. package/__perf__/utils/product-integration/sample-header.tsx +1 -3
  4. package/dist/cjs/common/constants.js +5 -1
  5. package/dist/cjs/components/resize-control/index.js +16 -24
  6. package/dist/cjs/components/resize-control/resize-button.js +10 -4
  7. package/dist/cjs/components/skip-links/skip-link-components.js +1 -1
  8. package/dist/cjs/components/slots/internal/left-sidebar-inner.js +29 -2
  9. package/dist/cjs/components/slots/internal/left-sidebar-outer.js +40 -7
  10. package/dist/cjs/components/slots/internal/resizable-children-wrapper.js +1 -1
  11. package/dist/cjs/components/slots/left-sidebar.js +137 -39
  12. package/dist/cjs/components/slots/main.js +1 -1
  13. package/dist/cjs/components/slots/page-layout.js +10 -1
  14. package/dist/cjs/components/slots/slot-dimensions.js +5 -2
  15. package/dist/cjs/controllers/sidebar-resize-context.js +2 -1
  16. package/dist/cjs/controllers/sidebar-resize-controller.js +96 -35
  17. package/dist/cjs/version.json +1 -1
  18. package/dist/es2019/common/constants.js +2 -0
  19. package/dist/es2019/components/resize-control/index.js +15 -23
  20. package/dist/es2019/components/resize-control/resize-button.js +13 -4
  21. package/dist/es2019/components/skip-links/skip-link-components.js +1 -1
  22. package/dist/es2019/components/slots/internal/left-sidebar-inner.js +34 -3
  23. package/dist/es2019/components/slots/internal/left-sidebar-outer.js +51 -8
  24. package/dist/es2019/components/slots/internal/resizable-children-wrapper.js +1 -1
  25. package/dist/es2019/components/slots/left-sidebar.js +142 -42
  26. package/dist/es2019/components/slots/main.js +1 -1
  27. package/dist/es2019/components/slots/page-layout.js +17 -1
  28. package/dist/es2019/components/slots/slot-dimensions.js +5 -2
  29. package/dist/es2019/controllers/sidebar-resize-context.js +2 -1
  30. package/dist/es2019/controllers/sidebar-resize-controller.js +96 -38
  31. package/dist/es2019/version.json +1 -1
  32. package/dist/esm/common/constants.js +2 -0
  33. package/dist/esm/components/resize-control/index.js +15 -23
  34. package/dist/esm/components/resize-control/resize-button.js +10 -4
  35. package/dist/esm/components/skip-links/skip-link-components.js +1 -1
  36. package/dist/esm/components/slots/internal/left-sidebar-inner.js +29 -3
  37. package/dist/esm/components/slots/internal/left-sidebar-outer.js +42 -9
  38. package/dist/esm/components/slots/internal/resizable-children-wrapper.js +1 -1
  39. package/dist/esm/components/slots/left-sidebar.js +140 -42
  40. package/dist/esm/components/slots/main.js +1 -1
  41. package/dist/esm/components/slots/page-layout.js +10 -1
  42. package/dist/esm/components/slots/slot-dimensions.js +5 -2
  43. package/dist/esm/controllers/sidebar-resize-context.js +2 -1
  44. package/dist/esm/controllers/sidebar-resize-controller.js +96 -35
  45. package/dist/esm/version.json +1 -1
  46. package/dist/types/common/constants.d.ts +2 -0
  47. package/dist/types/components/resize-control/types.d.ts +0 -2
  48. package/dist/types/components/slots/internal/left-sidebar-outer.d.ts +1 -1
  49. package/dist/types/components/slots/left-sidebar.d.ts +6 -0
  50. package/dist/types/components/slots/slot-dimensions.d.ts +2 -1
  51. package/dist/types/controllers/sidebar-resize-context.d.ts +10 -0
  52. package/dist/types-ts4.5/common/constants.d.ts +2 -0
  53. package/dist/types-ts4.5/components/resize-control/types.d.ts +0 -2
  54. package/dist/types-ts4.5/components/slots/internal/left-sidebar-outer.d.ts +1 -1
  55. package/dist/types-ts4.5/components/slots/left-sidebar.d.ts +6 -0
  56. package/dist/types-ts4.5/components/slots/slot-dimensions.d.ts +2 -1
  57. package/dist/types-ts4.5/controllers/sidebar-resize-context.d.ts +10 -0
  58. package/package.json +12 -6
  59. package/report.api.md +7 -6
  60. package/tmp/api-report-tmp.d.ts +174 -0
@@ -1,8 +1,13 @@
1
1
  /* eslint-disable @repo/internal/dom-events/no-unsafe-event-listeners */
2
2
  /** @jsx jsx */
3
- import { useContext, useEffect, useRef } from 'react';
4
- import { jsx } from '@emotion/react';
5
- import { COLLAPSED_LEFT_SIDEBAR_WIDTH, DEFAULT_LEFT_SIDEBAR_WIDTH, FLYOUT_DELAY, RESIZE_BUTTON_SELECTOR, VAR_LEFT_SIDEBAR_FLYOUT, VAR_LEFT_SIDEBAR_WIDTH } from '../../common/constants';
3
+ import { Fragment, useCallback, useContext, useEffect, useRef } from 'react';
4
+ import { css, jsx } from '@emotion/react';
5
+ import useCloseOnEscapePress from '@atlaskit/ds-lib/use-close-on-escape-press';
6
+ import { easeOut } from '@atlaskit/motion';
7
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
8
+ import { UNSAFE_useMediaQuery as useMediaQuery } from '@atlaskit/primitives/responsive';
9
+ import { N100A } from '@atlaskit/theme/colors';
10
+ import { COLLAPSED_LEFT_SIDEBAR_WIDTH, DEFAULT_LEFT_SIDEBAR_WIDTH, FLYOUT_DELAY, MOBILE_COLLAPSED_LEFT_SIDEBAR_WIDTH, RESIZE_BUTTON_SELECTOR, TRANSITION_DURATION, VAR_LEFT_SIDEBAR_FLYOUT, VAR_LEFT_SIDEBAR_WIDTH } from '../../common/constants';
6
11
  import { getGridStateFromStorage, mergeGridStateIntoStorage, resolveDimension } from '../../common/utils';
7
12
  import { publishGridState, SidebarResizeContext, useSkipLink } from '../../controllers';
8
13
  import ResizeControl from '../resize-control';
@@ -11,11 +16,32 @@ import LeftSidebarOuter from './internal/left-sidebar-outer';
11
16
  import ResizableChildrenWrapper from './internal/resizable-children-wrapper';
12
17
  import SlotDimensions from './slot-dimensions';
13
18
 
19
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage -- With a feature flag, this does not apply
20
+ const openBackdropStyles = getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') ? css({
21
+ width: '100%',
22
+ height: '100%',
23
+ position: 'absolute',
24
+ background: `var(--ds-blanket, ${N100A})`,
25
+ opacity: 1
26
+ }) : undefined;
27
+
28
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage -- With a feature flag, this does not apply
29
+ const hiddenBackdropStyles = getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') ? css({
30
+ opacity: 0,
31
+ transition: `opacity ${TRANSITION_DURATION}ms ${easeOut} 0s`
32
+ }) : undefined;
33
+
14
34
  /**
15
35
  * __Left sidebar__
16
36
  *
17
37
  * Provides a slot for a left sidebar within the PageLayout.
18
38
  *
39
+ * [Behind a feature-flag 'platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g']:
40
+ * On smaller viewports, the left sidebar can no longer be expanded. Instead, expanding it will
41
+ * put it into our "flyout mode" to lay overtop (which in desktop is explicitly a hover state).
42
+ * This ensures the contents behind do not reflow oddly and allows for a better experience
43
+ * resizing between mobile and desktop.
44
+ *
19
45
  * - [Examples](https://atlassian.design/components/page-layout/examples)
20
46
  * - [Code](https://atlassian.design/components/page-layout/code)
21
47
  */
@@ -75,10 +101,10 @@ const LeftSidebar = props => {
75
101
  }
76
102
  if (!isLocked && handlerRef.current) {
77
103
  if (mouseXRef.current >= lastLeftSidebarWidth) {
78
- setLeftSidebarState({
79
- ...leftSidebarState,
104
+ setLeftSidebarState(current => ({
105
+ ...current,
80
106
  isFlyoutOpen: false
81
- });
107
+ }));
82
108
  }
83
109
  document.removeEventListener('mousemove', handlerRef.current);
84
110
  handlerRef.current = null;
@@ -88,7 +114,7 @@ const LeftSidebar = props => {
88
114
  document.removeEventListener('mousemove', handlerRef.current);
89
115
  }
90
116
  };
91
- }, [isLocked, lastLeftSidebarWidth, leftSidebarState, setLeftSidebarState]);
117
+ }, [isLocked, lastLeftSidebarWidth, setLeftSidebarState]);
92
118
  const _width = Math.max(width || 0, DEFAULT_LEFT_SIDEBAR_WIDTH);
93
119
  const collapsedStateOverrideOpen = collapsedState === 'expanded';
94
120
  let leftSidebarWidthOnMount;
@@ -196,40 +222,114 @@ const LeftSidebar = props => {
196
222
  }));
197
223
  }, FLYOUT_DELAY);
198
224
  };
199
- return (
200
- // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
201
- jsx(LeftSidebarOuter, {
202
- ref: leftSideBarRef,
203
- testId: testId,
204
- onMouseOver: onMouseOver,
205
- onMouseLeave: onMouseLeave,
206
- id: id,
207
- isFixed: isFixed,
208
- isFlyoutOpen: isFlyoutOpen
209
- }, jsx(SlotDimensions, {
210
- variableName: VAR_LEFT_SIDEBAR_WIDTH,
211
- value: notFirstRun.current ? leftSidebarWidth : leftSidebarWidthOnMount
212
- }), jsx(LeftSidebarInner, {
213
- isFixed: isFixed,
214
- isFlyoutOpen: isFlyoutOpen
215
- }, jsx(ResizableChildrenWrapper, {
216
- isFlyoutOpen: isFlyoutOpen,
217
- isLeftSidebarCollapsed: isLeftSidebarCollapsed,
218
- hasCollapsedState: !notFirstRun.current && collapsedState === 'collapsed'
219
- }, children), jsx(ResizeControl, {
220
- testId: testId,
221
- resizeGrabAreaLabel: resizeGrabAreaLabel,
222
- resizeButtonLabel: resizeButtonLabel
223
- // eslint-disable-next-line @repo/internal/react/no-unsafe-overrides
224
- ,
225
- overrides: overrides,
226
- onCollapse: onCollapse,
227
- onExpand: onExpand,
228
- onResizeStart: onResizeStart,
229
- onResizeEnd: onResizeEnd,
230
- leftSidebarState: leftSidebarState,
231
- setLeftSidebarState: setLeftSidebarState
232
- })))
233
- );
225
+ const mobileMediaQuery = getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') ?
226
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- Does not apply to being feature flagged.
227
+ useMediaQuery('below.md') : null;
228
+ const openMobileFlyout = getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') ?
229
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- Does not apply to being feature flagged.
230
+ useCallback(() => {
231
+ if (!(mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches)) {
232
+ return;
233
+ }
234
+ setLeftSidebarState(current => {
235
+ if (current.isFlyoutOpen) {
236
+ return current;
237
+ }
238
+ onExpand === null || onExpand === void 0 ? void 0 : onExpand();
239
+ return {
240
+ ...current,
241
+ isFlyoutOpen: true
242
+ };
243
+ });
244
+ }, [setLeftSidebarState, onExpand, mobileMediaQuery]) : undefined;
245
+ const closeMobileFlyout = getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') ?
246
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- Does not apply to being feature flagged.
247
+ useCallback(() => {
248
+ if (!(mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches)) {
249
+ return;
250
+ }
251
+ setLeftSidebarState(current => {
252
+ if (!current.isFlyoutOpen) {
253
+ return current;
254
+ }
255
+ onCollapse === null || onCollapse === void 0 ? void 0 : onCollapse();
256
+ return {
257
+ ...current,
258
+ isFlyoutOpen: false
259
+ };
260
+ });
261
+ }, [setLeftSidebarState, onCollapse, mobileMediaQuery]) : undefined;
262
+ if (getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g')) {
263
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- Does not apply to being feature flagged.
264
+ useMediaQuery('below.md', event => {
265
+ setLeftSidebarState(current => {
266
+ if (event.matches && !current.isLeftSidebarCollapsed) {
267
+ // Sidebar was previously open when resizing downwards, convert the sidebar being open to a flyout being open
268
+ return {
269
+ ...current,
270
+ isResizing: false,
271
+ isLeftSidebarCollapsed: true,
272
+ leftSidebarWidth: COLLAPSED_LEFT_SIDEBAR_WIDTH,
273
+ lastLeftSidebarWidth: current.leftSidebarWidth,
274
+ isFlyoutOpen: true
275
+ };
276
+ } else if (!event.matches && current.isFlyoutOpen) {
277
+ // The user is resizing "upwards", eg. going from mobile to desktop.
278
+ // Flyout was previously open, let's keep it open by moving to the un-collapsed sidebar instead
279
+
280
+ return {
281
+ ...current,
282
+ isResizing: false,
283
+ isLeftSidebarCollapsed: false,
284
+ leftSidebarWidth: Math.max(current.lastLeftSidebarWidth, DEFAULT_LEFT_SIDEBAR_WIDTH),
285
+ isFlyoutOpen: false
286
+ };
287
+ }
288
+ return current;
289
+ });
290
+ });
291
+
292
+ // Close the flyout when the "escape" key is pressed.
293
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- Does not apply to being feature flagged.
294
+ useCloseOnEscapePress({
295
+ onClose: closeMobileFlyout,
296
+ isDisabled: !isFlyoutOpen
297
+ });
298
+ }
299
+ return jsx(Fragment, null, (mobileMediaQuery === null || mobileMediaQuery === void 0 ? void 0 : mobileMediaQuery.matches) && getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') && jsx("div", {
300
+ "aria-hidden": "true",
301
+ css: [hiddenBackdropStyles, isFlyoutOpen && openBackdropStyles],
302
+ onClick: closeMobileFlyout
303
+ }), jsx(LeftSidebarOuter, {
304
+ ref: leftSideBarRef,
305
+ testId: testId,
306
+ onMouseOver: !(mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches) ? onMouseOver : undefined,
307
+ onMouseLeave: !(mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches) ? onMouseLeave : undefined,
308
+ onClick: mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches ? openMobileFlyout : undefined,
309
+ id: id,
310
+ isFixed: isFixed
311
+ }, jsx(SlotDimensions, {
312
+ variableName: VAR_LEFT_SIDEBAR_WIDTH,
313
+ value: notFirstRun.current ? leftSidebarWidth : leftSidebarWidthOnMount,
314
+ mobileValue: MOBILE_COLLAPSED_LEFT_SIDEBAR_WIDTH
315
+ }), jsx(LeftSidebarInner, {
316
+ isFixed: isFixed,
317
+ isFlyoutOpen: isFlyoutOpen
318
+ }, jsx(ResizableChildrenWrapper, {
319
+ isFlyoutOpen: isFlyoutOpen,
320
+ isLeftSidebarCollapsed: isLeftSidebarCollapsed,
321
+ hasCollapsedState: !notFirstRun.current && collapsedState === 'collapsed'
322
+ }, children), jsx(ResizeControl, {
323
+ testId: testId,
324
+ resizeGrabAreaLabel: resizeGrabAreaLabel,
325
+ resizeButtonLabel: resizeButtonLabel
326
+ // eslint-disable-next-line @repo/internal/react/no-unsafe-overrides
327
+ ,
328
+ overrides: overrides,
329
+ onCollapse: onCollapse,
330
+ onExpand: onExpand,
331
+ onResizeStart: onResizeStart,
332
+ onResizeEnd: onResizeEnd
333
+ }))));
234
334
  };
235
335
  export default LeftSidebar;
@@ -10,7 +10,7 @@ import { getPageLayoutSlotSelector } from '../../common/utils';
10
10
  import { SidebarResizeContext, useSkipLink } from '../../controllers';
11
11
  import SlotFocusRing from './internal/slot-focus-ring';
12
12
 
13
- // eslint-disable-next-line @repo/internal/react/consistent-css-prop-usage
13
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage
14
14
  const prefersReducedMotionStyles = css(prefersReducedMotion());
15
15
  const mainStyles = css({
16
16
  minWidth: 0,
@@ -2,12 +2,19 @@ import _extends from "@babel/runtime/helpers/extends";
2
2
  /** @jsx jsx */
3
3
  import { Fragment } from 'react';
4
4
  import { css, jsx } from '@emotion/react';
5
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
6
+ import { UNSAFE_media as media } from '@atlaskit/primitives/responsive';
5
7
  import { BANNER, BANNER_HEIGHT, CONTENT, DEFAULT_I18N_PROPS_SKIP_LINKS, LEFT_PANEL, LEFT_PANEL_WIDTH, PAGE_LAYOUT_CONTAINER_SELECTOR, RIGHT_PANEL, RIGHT_PANEL_WIDTH, TOP_NAVIGATION, TOP_NAVIGATION_HEIGHT } from '../../common/constants';
6
8
  import { SidebarResizeController, SkipLinksController } from '../../controllers';
7
9
  import { SkipLinkWrapper } from '../skip-links';
8
10
  const pageLayoutSelector = {
9
11
  [PAGE_LAYOUT_CONTAINER_SELECTOR]: true
10
12
  };
13
+ const gridTemplateAreasMobile = `
14
+ "${LEFT_PANEL} ${BANNER}"
15
+ "${LEFT_PANEL} ${TOP_NAVIGATION}"
16
+ "${LEFT_PANEL} ${CONTENT}"
17
+ `;
11
18
  const gridTemplateAreas = `
12
19
  "${LEFT_PANEL} ${BANNER} ${RIGHT_PANEL}"
13
20
  "${LEFT_PANEL} ${TOP_NAVIGATION} ${RIGHT_PANEL}"
@@ -22,6 +29,15 @@ const gridStyles = css({
22
29
  outline: 'none'
23
30
  });
24
31
 
32
+ // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage -- With a feature flag, this does not apply
33
+ const gridStylesMobile = getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') ? css({
34
+ // eslint-disable-next-line @repo/internal/styles/no-nested-styles
35
+ [media.below.md]: {
36
+ gridTemplateAreas: gridTemplateAreasMobile,
37
+ gridTemplateColumns: `${LEFT_PANEL_WIDTH} minmax(0, 1fr)`
38
+ }
39
+ }) : undefined;
40
+
25
41
  /**
26
42
  * __Page layout__
27
43
  *
@@ -41,7 +57,7 @@ const PageLayout = ({
41
57
  skipLinksLabel: skipLinksLabel
42
58
  }), jsx("div", _extends({}, pageLayoutSelector, {
43
59
  "data-testid": testId,
44
- css: gridStyles,
60
+ css: [gridStyles, gridStylesMobile],
45
61
  tabIndex: -1
46
62
  }), jsx(SidebarResizeController, {
47
63
  onLeftSidebarCollapse: onLeftSidebarCollapse,
@@ -1,5 +1,8 @@
1
1
  import React from 'react';
2
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
3
+ import { UNSAFE_media as media } from '@atlaskit/primitives/responsive';
2
4
  export default (({
3
5
  variableName,
4
- value
5
- }) => /*#__PURE__*/React.createElement("style", null, `:root{--${variableName}:${value}px;}`));
6
+ value,
7
+ mobileValue
8
+ }) => /*#__PURE__*/React.createElement("style", null, `:root{--${variableName}:${value}px;}`, getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') && mobileValue && `${media.below.md} { :root{--${variableName}:${mobileValue}px;} }`));
@@ -15,7 +15,8 @@ export const SidebarResizeContext = /*#__PURE__*/createContext({
15
15
  expandLeftSidebar: noop,
16
16
  collapseLeftSidebar: noop,
17
17
  leftSidebarState,
18
- setLeftSidebarState: noop
18
+ setLeftSidebarState: noop,
19
+ toggleLeftSidebar: noop
19
20
  });
20
21
  export const usePageLayoutResize = () => {
21
22
  const {
@@ -2,10 +2,12 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import { bind } from 'bind-event-listener';
3
3
  import noop from '@atlaskit/ds-lib/noop';
4
4
  import { isReducedMotion } from '@atlaskit/motion';
5
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
6
+ import { UNSAFE_useMediaQuery as useMediaQuery } from '@atlaskit/primitives/responsive';
5
7
  import { COLLAPSED_LEFT_SIDEBAR_WIDTH, DEFAULT_LEFT_SIDEBAR_WIDTH, IS_SIDEBAR_COLLAPSING } from '../common/constants';
6
8
  import { getPageLayoutSlotCSSSelector } from '../common/utils';
7
9
  import { SidebarResizeContext } from './sidebar-resize-context';
8
- const handleDataAttributesAndCb = (callback = noop, isLeftSidebarCollapsed, leftSidebarState) => {
10
+ const handleDataAttributesAndCb = (callback = noop, leftSidebarState) => {
9
11
  document.documentElement.removeAttribute(IS_SIDEBAR_COLLAPSING);
10
12
  callback(leftSidebarState);
11
13
  };
@@ -26,7 +28,13 @@ export const SidebarResizeController = ({
26
28
  isFixed: true
27
29
  });
28
30
  const {
29
- isLeftSidebarCollapsed
31
+ leftSidebarWidth,
32
+ lastLeftSidebarWidth,
33
+ isResizing,
34
+ flyoutLockCount,
35
+ isFixed,
36
+ isLeftSidebarCollapsed,
37
+ isFlyoutOpen
30
38
  } = leftSidebarState;
31
39
 
32
40
  // We put the latest callbacks into a ref so we can always have the latest
@@ -40,25 +48,46 @@ export const SidebarResizeController = ({
40
48
  onExpand,
41
49
  onCollapse
42
50
  };
43
- });
51
+ }, [onExpand, onCollapse]);
44
52
  const transition = useRef(null);
53
+ const mobileMediaQuery = getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') ?
54
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- With the feature flag, this does not apply as it should be static.
55
+ useMediaQuery('below.md') : null;
56
+ const isOpen = mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches ? isFlyoutOpen : !isLeftSidebarCollapsed;
45
57
  const expandLeftSidebar = useCallback(() => {
46
- var _transition$current, _transition$current2;
47
- const {
48
- lastLeftSidebarWidth,
49
- isResizing,
50
- flyoutLockCount,
51
- isFixed,
52
- isLeftSidebarCollapsed
53
- } = leftSidebarState;
58
+ var _transition$current2, _transition$current3;
59
+ if (isOpen) {
60
+ return;
61
+ }
62
+
63
+ // If the user is at a mobile viewport when this runs, we handle it differently
64
+ // We don't expand at mobile widths; instead we use a flyout which is treated the same otherwise
65
+ if (mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches) {
66
+ var _transition$current;
67
+ const flyoutOpenSidebarState = {
68
+ isResizing: false,
69
+ isLeftSidebarCollapsed: true,
70
+ leftSidebarWidth: COLLAPSED_LEFT_SIDEBAR_WIDTH,
71
+ lastLeftSidebarWidth: leftSidebarWidth,
72
+ isFlyoutOpen: true,
73
+ flyoutLockCount: 0,
74
+ isFixed
75
+ };
76
+ setLeftSidebarState(flyoutOpenSidebarState);
77
+
78
+ // Flush the desktop transitions, cleanup, and call the `onExpand` still
79
+ (_transition$current = transition.current) === null || _transition$current === void 0 ? void 0 : _transition$current.complete();
80
+ handleDataAttributesAndCb(stableRef.current.onExpand, flyoutOpenSidebarState);
81
+ return;
82
+ }
54
83
  if (isResizing || !isLeftSidebarCollapsed ||
55
84
  // already expanding
56
- ((_transition$current = transition.current) === null || _transition$current === void 0 ? void 0 : _transition$current.action) === 'expand') {
85
+ ((_transition$current2 = transition.current) === null || _transition$current2 === void 0 ? void 0 : _transition$current2.action) === 'expand') {
57
86
  return;
58
87
  }
59
88
 
60
89
  // flush existing transition
61
- (_transition$current2 = transition.current) === null || _transition$current2 === void 0 ? void 0 : _transition$current2.complete();
90
+ (_transition$current3 = transition.current) === null || _transition$current3 === void 0 ? void 0 : _transition$current3.complete();
62
91
  const width = Math.max(lastLeftSidebarWidth, DEFAULT_LEFT_SIDEBAR_WIDTH);
63
92
  const updatedLeftSidebarState = {
64
93
  isLeftSidebarCollapsed: false,
@@ -71,9 +100,7 @@ export const SidebarResizeController = ({
71
100
  };
72
101
  setLeftSidebarState(updatedLeftSidebarState);
73
102
  function finish() {
74
- handleDataAttributesAndCb(stableRef.current.onExpand, false,
75
- // isCollapsed
76
- updatedLeftSidebarState);
103
+ handleDataAttributesAndCb(stableRef.current.onExpand, updatedLeftSidebarState);
77
104
  }
78
105
  const sidebar = document.querySelector(leftSidebarSelector);
79
106
  // onTransitionEnd isn't triggered when a user prefers reduced motion
@@ -85,8 +112,8 @@ export const SidebarResizeController = ({
85
112
  type: 'transitionend',
86
113
  listener(event) {
87
114
  if (event.target === sidebar && event.propertyName === 'width') {
88
- var _transition$current3;
89
- (_transition$current3 = transition.current) === null || _transition$current3 === void 0 ? void 0 : _transition$current3.complete();
115
+ var _transition$current4;
116
+ (_transition$current4 = transition.current) === null || _transition$current4 === void 0 ? void 0 : _transition$current4.complete();
90
117
  }
91
118
  }
92
119
  });
@@ -102,24 +129,41 @@ export const SidebarResizeController = ({
102
129
  }
103
130
  };
104
131
  transition.current = value;
105
- }, [leftSidebarState]);
132
+ }, [isOpen, mobileMediaQuery, isResizing, isLeftSidebarCollapsed, lastLeftSidebarWidth, flyoutLockCount, isFixed, leftSidebarWidth]);
106
133
  const collapseLeftSidebar = useCallback((event, collapseWithoutTransition) => {
107
- var _transition$current4, _transition$current5;
108
- const {
109
- leftSidebarWidth,
110
- isResizing,
111
- flyoutLockCount,
112
- isFixed,
113
- isLeftSidebarCollapsed
114
- } = leftSidebarState;
134
+ var _transition$current6, _transition$current7;
135
+ if (!isOpen) {
136
+ return;
137
+ }
138
+
139
+ // If the user is at a mobile viewport when this runs, we handle it differently
140
+ // We don't collapse at mobile widths; instead we close the flyout.
141
+ if (mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches) {
142
+ var _transition$current5;
143
+ const flyoutCloseSidebarState = {
144
+ isResizing: false,
145
+ isLeftSidebarCollapsed: true,
146
+ leftSidebarWidth: COLLAPSED_LEFT_SIDEBAR_WIDTH,
147
+ lastLeftSidebarWidth,
148
+ isFlyoutOpen: false,
149
+ flyoutLockCount: 0,
150
+ isFixed
151
+ };
152
+ setLeftSidebarState(flyoutCloseSidebarState);
153
+
154
+ // Flush the desktop transitions, cleanup, and call the `onCollapse` still
155
+ (_transition$current5 = transition.current) === null || _transition$current5 === void 0 ? void 0 : _transition$current5.complete();
156
+ handleDataAttributesAndCb(stableRef.current.onCollapse, flyoutCloseSidebarState);
157
+ return;
158
+ }
115
159
  if (isResizing || isLeftSidebarCollapsed ||
116
160
  // already collapsing
117
- ((_transition$current4 = transition.current) === null || _transition$current4 === void 0 ? void 0 : _transition$current4.action) === 'collapse') {
161
+ ((_transition$current6 = transition.current) === null || _transition$current6 === void 0 ? void 0 : _transition$current6.action) === 'collapse') {
118
162
  return;
119
163
  }
120
164
 
121
165
  // flush existing transition
122
- (_transition$current5 = transition.current) === null || _transition$current5 === void 0 ? void 0 : _transition$current5.complete();
166
+ (_transition$current7 = transition.current) === null || _transition$current7 === void 0 ? void 0 : _transition$current7.complete();
123
167
 
124
168
  // data-attribute is used as a CSS selector to sync the hiding/showing
125
169
  // of the nav contents with expand/collapse animation
@@ -135,7 +179,7 @@ export const SidebarResizeController = ({
135
179
  };
136
180
  setLeftSidebarState(updatedLeftSidebarState);
137
181
  function finish() {
138
- handleDataAttributesAndCb(stableRef.current.onCollapse, true, updatedLeftSidebarState);
182
+ handleDataAttributesAndCb(stableRef.current.onCollapse, updatedLeftSidebarState);
139
183
  }
140
184
  const sidebar = document.querySelector(leftSidebarSelector);
141
185
 
@@ -148,8 +192,8 @@ export const SidebarResizeController = ({
148
192
  type: 'transitionend',
149
193
  listener(event) {
150
194
  if (sidebar === event.target && event.propertyName === 'width') {
151
- var _transition$current6;
152
- (_transition$current6 = transition.current) === null || _transition$current6 === void 0 ? void 0 : _transition$current6.complete();
195
+ var _transition$current8;
196
+ (_transition$current8 = transition.current) === null || _transition$current8 === void 0 ? void 0 : _transition$current8.complete();
153
197
  }
154
198
  }
155
199
  });
@@ -165,22 +209,36 @@ export const SidebarResizeController = ({
165
209
  }
166
210
  };
167
211
  transition.current = value;
168
- }, [leftSidebarState]);
212
+ }, [isOpen, mobileMediaQuery, isResizing, isLeftSidebarCollapsed, leftSidebarWidth, flyoutLockCount, isFixed, lastLeftSidebarWidth]);
213
+
214
+ /**
215
+ * Conditionally toggle the expanding or collapsing the sidebars.
216
+ * This supports our mobile flyout mode as well.
217
+ */
218
+ const toggleLeftSidebar = useCallback((event, collapseWithoutTransition) => {
219
+ if (isOpen) {
220
+ collapseLeftSidebar(event, collapseWithoutTransition);
221
+ } else {
222
+ expandLeftSidebar();
223
+ }
224
+ }, [isOpen, expandLeftSidebar, collapseLeftSidebar]);
169
225
 
170
226
  // Make sure we finish any lingering transitions when unmounting
171
227
  useEffect(function mount() {
172
228
  return function unmount() {
173
- var _transition$current7;
174
- (_transition$current7 = transition.current) === null || _transition$current7 === void 0 ? void 0 : _transition$current7.abort();
229
+ var _transition$current9;
230
+ (_transition$current9 = transition.current) === null || _transition$current9 === void 0 ? void 0 : _transition$current9.abort();
175
231
  };
176
232
  }, []);
177
233
  const context = useMemo(() => ({
178
- isLeftSidebarCollapsed,
234
+ isLeftSidebarCollapsed: !isOpen,
235
+ // Technically this isn't quite true, but with mobile it's a bit safer if products are using this to roll their own collapse/expand
179
236
  expandLeftSidebar,
180
237
  collapseLeftSidebar,
181
238
  leftSidebarState,
182
- setLeftSidebarState
183
- }), [isLeftSidebarCollapsed, expandLeftSidebar, collapseLeftSidebar, leftSidebarState]);
239
+ setLeftSidebarState,
240
+ toggleLeftSidebar
241
+ }), [isOpen, expandLeftSidebar, collapseLeftSidebar, leftSidebarState, toggleLeftSidebar]);
184
242
  return /*#__PURE__*/React.createElement(SidebarResizeContext.Provider, {
185
243
  value: context
186
244
  }, children);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/page-layout",
3
- "version": "1.6.4",
3
+ "version": "1.7.0",
4
4
  "sideEffects": false
5
5
  }
@@ -28,9 +28,11 @@ export var DEFAULT_LEFT_PANEL_WIDTH = 368;
28
28
 
29
29
  // Other constants
30
30
  export var COLLAPSED_LEFT_SIDEBAR_WIDTH = 20;
31
+ export var MOBILE_COLLAPSED_LEFT_SIDEBAR_WIDTH = 16;
31
32
  export var MIN_LEFT_SIDEBAR_WIDTH = 80;
32
33
  export var DEFAULT_LEFT_SIDEBAR_FLYOUT_WIDTH = 240;
33
34
  export var MIN_LEFT_SIDEBAR_DRAG_THRESHOLD = 200;
35
+ export var MAX_MOBILE_SIDEBAR_FLYOUT_WIDTH = 350;
34
36
  export var TRANSITION_DURATION = 300;
35
37
  export var FLYOUT_DELAY = 200;
36
38
  export var LEFT_SIDEBAR_EXPANDED_WIDTH = 'expandedLeftSidebarWidth';
@@ -8,7 +8,9 @@ import { Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState
8
8
  import { css, Global, jsx } from '@emotion/react';
9
9
  import { bindAll } from 'bind-event-listener';
10
10
  import rafSchd from 'raf-schd';
11
- import { COLLAPSED_LEFT_SIDEBAR_WIDTH, DEFAULT_LEFT_SIDEBAR_WIDTH, IS_SIDEBAR_DRAGGING, MIN_LEFT_SIDEBAR_DRAG_THRESHOLD, RESIZE_BUTTON_SELECTOR, RESIZE_CONTROL_SELECTOR, VAR_LEFT_SIDEBAR_WIDTH } from '../../common/constants';
11
+ import { getBooleanFF } from '@atlaskit/platform-feature-flags';
12
+ import { UNSAFE_useMediaQuery as useMediaQuery } from '@atlaskit/primitives/responsive';
13
+ import { COLLAPSED_LEFT_SIDEBAR_WIDTH, DEFAULT_LEFT_SIDEBAR_WIDTH, IS_SIDEBAR_DRAGGING, MIN_LEFT_SIDEBAR_DRAG_THRESHOLD, RESIZE_CONTROL_SELECTOR, VAR_LEFT_SIDEBAR_WIDTH } from '../../common/constants';
12
14
  import { getLeftPanelWidth, getLeftSidebarPercentage } from '../../common/utils';
13
15
  import { SidebarResizeContext } from '../../controllers/sidebar-resize-context';
14
16
  /* import useUpdateCssVar from '../../controllers/use-update-css-vars'; */
@@ -65,12 +67,11 @@ var ResizeControl = function ResizeControl(_ref) {
65
67
  onResizeStart = _ref.onResizeStart,
66
68
  onResizeEnd = _ref.onResizeEnd;
67
69
  var _useContext = useContext(SidebarResizeContext),
68
- expandLeftSidebar = _useContext.expandLeftSidebar,
70
+ toggleLeftSidebar = _useContext.toggleLeftSidebar,
69
71
  collapseLeftSidebar = _useContext.collapseLeftSidebar,
70
72
  leftSidebarState = _useContext.leftSidebarState,
71
73
  setLeftSidebarState = _useContext.setLeftSidebarState;
72
- var isLeftSidebarCollapsed = leftSidebarState.isLeftSidebarCollapsed,
73
- isResizing = leftSidebarState.isResizing;
74
+ var isLeftSidebarCollapsed = leftSidebarState.isLeftSidebarCollapsed;
74
75
  var sidebarWidth = useRef(leftSidebarState[VAR_LEFT_SIDEBAR_WIDTH]);
75
76
  // Distance of mouse from left sidebar onMouseDown
76
77
  var offset = useRef(0);
@@ -80,6 +81,9 @@ var ResizeControl = function ResizeControl(_ref) {
80
81
  isGrabAreaFocused = _useState2[0],
81
82
  setIsGrabAreaFocused = _useState2[1];
82
83
  var unbindEvents = useRef(null);
84
+ var mobileMediaQuery = getBooleanFF('platform.design-system-team.responsive-page-layout-left-sidebar_p8r7g') ?
85
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- With the feature flag, this does not apply as it should be static.
86
+ useMediaQuery('below.md') : null;
83
87
 
84
88
  // Used in some cases to ensure function references don't have to change
85
89
  // TODO: more functions could use `stableSidebarState` rather than `leftSidebarState`
@@ -87,23 +91,11 @@ var ResizeControl = function ResizeControl(_ref) {
87
91
  useEffect(function () {
88
92
  stableSidebarState.current = leftSidebarState;
89
93
  }, [leftSidebarState]);
90
- var toggleSideBar = function toggleSideBar(e) {
91
- if (isResizing) {
92
- return;
93
- }
94
- if (isLeftSidebarCollapsed) {
95
- expandLeftSidebar();
96
- } else {
97
- collapseLeftSidebar();
98
- }
99
-
100
- // Bring focus to the resize button if the grab area is
101
- // "clicked" using enter/space on keyboard.
102
- if (e && e.nativeEvent.detail === 0) {
103
- var _resizeButton = document.querySelector("[".concat(RESIZE_BUTTON_SELECTOR, "]"));
104
- _resizeButton && _resizeButton.focus();
105
- }
106
- };
94
+ var toggleSideBar = useCallback(function (event) {
95
+ // don't cascade down to the LeftSidebarOuter
96
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
97
+ toggleLeftSidebar();
98
+ }, [toggleLeftSidebar]);
107
99
  var onMouseDown = function onMouseDown(event) {
108
100
  if (isLeftSidebarCollapsed) {
109
101
  return;
@@ -335,7 +327,7 @@ var ResizeControl = function ResizeControl(_ref) {
335
327
  css: [resizeControlStyles, (isGrabAreaFocused || isLeftSidebarCollapsed) && showResizeButtonStyles]
336
328
  }), jsx(Shadow, {
337
329
  testId: testId && "".concat(testId, "-shadow")
338
- }), jsx(GrabArea, {
330
+ }), !(mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches) && jsx(GrabArea, {
339
331
  role: "separator",
340
332
  "aria-label": resizeGrabAreaLabel,
341
333
  "aria-valuenow": leftSidebarPercentageExpanded,
@@ -350,7 +342,7 @@ var ResizeControl = function ResizeControl(_ref) {
350
342
  isLeftSidebarCollapsed: isLeftSidebarCollapsed,
351
343
  disabled: isLeftSidebarCollapsed
352
344
  }), resizeButton.render(ResizeButton, {
353
- isLeftSidebarCollapsed: isLeftSidebarCollapsed,
345
+ isLeftSidebarCollapsed: mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches ? !leftSidebarState.isFlyoutOpen : isLeftSidebarCollapsed,
354
346
  label: resizeButtonLabel,
355
347
  onClick: toggleSideBar,
356
348
  testId: testId && "".concat(testId, "-resize-button")