@atlaskit/navigation-system 2.19.1 → 2.20.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 (24) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs/ui/page-layout/side-nav/toggle-button-context.js +2 -7
  3. package/dist/cjs/ui/page-layout/side-nav/toggle-button-provider.js +1 -13
  4. package/dist/cjs/ui/page-layout/side-nav/toggle-button.compiled.css +1 -2
  5. package/dist/cjs/ui/page-layout/side-nav/toggle-button.js +1 -18
  6. package/dist/cjs/ui/page-layout/top-nav/top-nav-start.compiled.css +6 -1
  7. package/dist/cjs/ui/page-layout/top-nav/top-nav-start.js +120 -8
  8. package/dist/es2019/ui/page-layout/side-nav/toggle-button-context.js +1 -6
  9. package/dist/es2019/ui/page-layout/side-nav/toggle-button-provider.js +1 -14
  10. package/dist/es2019/ui/page-layout/side-nav/toggle-button.compiled.css +1 -2
  11. package/dist/es2019/ui/page-layout/side-nav/toggle-button.js +2 -19
  12. package/dist/es2019/ui/page-layout/top-nav/top-nav-start.compiled.css +6 -1
  13. package/dist/es2019/ui/page-layout/top-nav/top-nav-start.js +117 -8
  14. package/dist/esm/ui/page-layout/side-nav/toggle-button-context.js +1 -6
  15. package/dist/esm/ui/page-layout/side-nav/toggle-button-provider.js +1 -13
  16. package/dist/esm/ui/page-layout/side-nav/toggle-button.compiled.css +1 -2
  17. package/dist/esm/ui/page-layout/side-nav/toggle-button.js +2 -19
  18. package/dist/esm/ui/page-layout/top-nav/top-nav-start.compiled.css +6 -1
  19. package/dist/esm/ui/page-layout/top-nav/top-nav-start.js +120 -8
  20. package/dist/types/ui/page-layout/side-nav/toggle-button-context.d.ts +0 -4
  21. package/dist/types/ui/page-layout/side-nav/toggle-button-provider.d.ts +0 -8
  22. package/dist/types-ts4.5/ui/page-layout/side-nav/toggle-button-context.d.ts +0 -4
  23. package/dist/types-ts4.5/ui/page-layout/side-nav/toggle-button-provider.d.ts +0 -8
  24. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @atlassian/navigation-system
2
2
 
3
+ ## 2.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`bfed073f1849d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/bfed073f1849d) -
8
+ Animations have been added to the `TopNavStart` component, as part of the full height sidebar
9
+ animations. The reorder of `TopNavStart`'s children elements (toggle button, app switcher, app
10
+ logo) when the side nav is toggled (on desktop) will have slide animations.
11
+
12
+ These changes are behind the feature gate `navx-full-height-sidebar`.
13
+
3
14
  ## 2.19.1
4
15
 
5
16
  ### Patch Changes
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.SideNavToggleButtonSlotContext = exports.SideNavToggleButtonElement = exports.SideNavToggleButtonAttachRef = void 0;
7
+ exports.SideNavToggleButtonElement = exports.SideNavToggleButtonAttachRef = void 0;
8
8
  var _react = require("react");
9
9
  var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop"));
10
10
  /**
@@ -23,9 +23,4 @@ var SideNavToggleButtonElement = exports.SideNavToggleButtonElement = /*#__PURE_
23
23
  * A callback ref is needed because the side nav can be mounted before the elements in the top bar (e.g. if the element
24
24
  * is lazy loaded, which happens in Jira and Confluence), which would prevent the event listeners from being set up.
25
25
  */
26
- var SideNavToggleButtonAttachRef = exports.SideNavToggleButtonAttachRef = /*#__PURE__*/(0, _react.createContext)(_noop.default);
27
-
28
- /**
29
- * Used to check if the SideNavToggleButton is rendered inside of its slot in `TopNavStart`.
30
- */
31
- var SideNavToggleButtonSlotContext = exports.SideNavToggleButtonSlotContext = /*#__PURE__*/(0, _react.createContext)(false);
26
+ var SideNavToggleButtonAttachRef = exports.SideNavToggleButtonAttachRef = /*#__PURE__*/(0, _react.createContext)(_noop.default);
@@ -5,7 +5,7 @@ var _typeof = require("@babel/runtime/helpers/typeof");
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.SideNavToggleButtonSlotProvider = exports.SideNavToggleButtonProvider = void 0;
8
+ exports.SideNavToggleButtonProvider = void 0;
9
9
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
10
  var _react = _interopRequireWildcard(require("react"));
11
11
  var _toggleButtonContext = require("./toggle-button-context");
@@ -34,16 +34,4 @@ var SideNavToggleButtonProvider = exports.SideNavToggleButtonProvider = function
34
34
  }, /*#__PURE__*/_react.default.createElement(_toggleButtonContext.SideNavToggleButtonAttachRef.Provider, {
35
35
  value: setElement
36
36
  }, children));
37
- };
38
-
39
- /**
40
- * Provider for the side nav toggle button slot.
41
- *
42
- * This allows us to determine if the toggle button is rendered inside or outside of its slot.
43
- */
44
- var SideNavToggleButtonSlotProvider = exports.SideNavToggleButtonSlotProvider = function SideNavToggleButtonSlotProvider(_ref2) {
45
- var children = _ref2.children;
46
- return /*#__PURE__*/_react.default.createElement(_toggleButtonContext.SideNavToggleButtonSlotContext.Provider, {
47
- value: true
48
- }, children);
49
37
  };
@@ -1,3 +1,2 @@
1
1
  ._1e0c1bgi{display:contents}
2
- ._lcxvglyw{pointer-events:none}
3
- @media (min-width:64rem){._3l1a1wug{margin-inline-start:auto}}
2
+ ._lcxvglyw{pointer-events:none}
@@ -32,12 +32,6 @@ var toggleButtonTooltipOptions = {
32
32
  // For duplicate "mouseenter" issue when changing icons (see below)
33
33
  var silentIconStyles = null;
34
34
 
35
- /**
36
- * Wrapper styles to align the toggle button to the end of `TopNavStart`
37
- * when FHS is expanded.
38
- */
39
- var fullHeightSidebarExpandedWrapperStyles = null;
40
-
41
35
  /**
42
36
  * __SideNavToggleButton__
43
37
  *
@@ -144,7 +138,7 @@ var SideNavToggleButton = exports.SideNavToggleButton = function SideNavToggleBu
144
138
  }
145
139
  return toggleButtonTooltipOptions;
146
140
  }, [shortcut]);
147
- var iconButton = /*#__PURE__*/_react.default.createElement(_migration.IconButton, {
141
+ return /*#__PURE__*/_react.default.createElement(_migration.IconButton, {
148
142
  appearance: "subtle",
149
143
  label: isSideNavExpanded ? collapseLabel : expandLabel,
150
144
  icon: icon,
@@ -155,15 +149,4 @@ var SideNavToggleButton = exports.SideNavToggleButton = function SideNavToggleBu
155
149
  ref: (0, _platformFeatureFlags.fg)('platform_dst_nav4_side_nav_toggle_ref_fix') ? setElement : elementRef,
156
150
  tooltip: tooltipProps
157
151
  });
158
- var isInsideSlot = (0, _react.useContext)(_toggleButtonContext.SideNavToggleButtonSlotContext);
159
-
160
- // Checking `isInsideSlot` in case an app isn't using the slot
161
- // We don't want to break existing non-slot usage with the left margin
162
- // This check can be removed in the future, after slot is required for a while.
163
- if (isInsideSlot && (0, _platformFeatureFlags.fg)('navx-full-height-sidebar')) {
164
- return /*#__PURE__*/_react.default.createElement("div", {
165
- className: (0, _runtime.ax)([isSideNavExpandedOnDesktop && "_3l1a1wug"])
166
- }, iconButton);
167
- }
168
- return iconButton;
169
152
  };
@@ -1,9 +1,14 @@
1
1
 
2
2
  ._zulp1b66{gap:var(--ds-space-050,4px)}
3
- ._yyhykb7n{grid-column:1}._1e0c1txw{display:flex}
3
+ ._zulp1kw7{gap:inherit}
4
+ ._yyhykb7n{grid-column:1}._1e0c1kw7{display:inherit}
5
+ ._1e0c1txw{display:flex}
6
+ ._1ul9idpf{min-width:0}
4
7
  ._4cvr1h6o{align-items:center}
5
8
  ._4t3i1osq{height:100%}
9
+ ._ahbq1wug{margin-inline-start:auto}
6
10
  ._bozgutpp{padding-inline-start:var(--ds-space-150,9pt)}
7
11
  ._lcxv1wug{pointer-events:auto}
8
12
  ._vchhusvi{box-sizing:border-box}
13
+ @media (prefers-reduced-motion:no-preference){._10t81e03{transition-property:transform}._10t81rjc{transition-property:transform,opacity}._1xq51ytf{transition-timing-function:ease-in-out}._1xq55ucs{transition-timing-function:ease}._mjvc162w{transform:translateX(calc(-2rem + var(--ds-space-050, 4px)*-1))}._mjvcjq3t{transform:translateX(-100%)}._bgpzidpf{opacity:0}._mjvcsws1{transform:translateX(calc(2rem + var(--ds-space-050, 4px)))}._mjvcxwn4{transform:translateX(100%)}._mjvcz12g{transform:translateX(0)}._xrrp188d{transition-duration:.3s}._bgpzkb7n{opacity:1}}
9
14
  @media (min-width:64rem){._15rin7od{min-width:unset}._glte1osq{width:100%}._15rip2n4{min-width:330px}._glte1ris{width:max-content}._15ri1mjv{min-width:300px}._1gs5usvi{box-sizing:border-box}._glte93mn{width:var(--n_sNvlw,100%)}._exxmpxbi{padding-inline-end:var(--ds-space-200,1pc)}}
@@ -11,12 +11,24 @@ require("./top-nav-start.compiled.css");
11
11
  var _runtime = require("@compiled/react/runtime");
12
12
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
13
13
  var _react = _interopRequireWildcard(require("react"));
14
+ var _useStableRef = _interopRequireDefault(require("@atlaskit/ds-lib/use-stable-ref"));
14
15
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
16
  var _compiled = require("@atlaskit/primitives/compiled");
16
17
  var _topNavStartContext = require("../../../context/top-nav-start/top-nav-start-context");
17
- var _toggleButtonProvider = require("../side-nav/toggle-button-provider");
18
18
  var _useSideNavVisibility3 = require("../side-nav/use-side-nav-visibility");
19
19
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
20
+ /**
21
+ * Firefox does support these reorder animations, but only partially enabling layout animations would look odd.
22
+ *
23
+ * We are using JS to detect Firefox and disable animations, instead of using CSS, as Compiled currently does not merge duplicate
24
+ * CSS at-rules when at-rules are nested: https://github.com/atlassian-labs/compiled/blob/e04a325915e1d13010205089e4915de0e53bc2d4/packages/css/src/plugins/merge-duplicate-at-rules.ts#L5
25
+ * Avoiding nesting the `@supports` at-rule inside of `@media` means Compiled can remove duplicate styles from the generated CSS.
26
+ */
27
+ var isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
28
+
29
+ // Placed in a variable, as the value is used in the translateX value for the children wrapper animation.
30
+ var flexGap = "var(--ds-space-050, 4px)";
31
+
20
32
  /**
21
33
  * Styles for the TopNavStart element.
22
34
  *
@@ -39,10 +51,48 @@ var innerStyles = {
39
51
  var wrapperStyles = {
40
52
  root: "_vchhusvi",
41
53
  fullHeightSidebar: "_bozgutpp",
42
- fullHeightSidebarCollapsed: "_15rip2n4",
43
54
  fullHeightSidebarExpanded: "_glte93mn _exxmpxbi"
44
55
  };
45
56
 
57
+ /**
58
+ * We use a fixed translateX offset for the slide animation (used when the TopNavStart children elements are reordered).
59
+ * This fixed offset makes the elements appear to animate smoothly from the old to the new position.
60
+ * This offset is calculated based on:
61
+ * - 32px (2rem) width of the side nav toggle button (IconButton)
62
+ * - 4px gap ('space.050' token) of the flex container
63
+ *
64
+ * The benefit of hardcoding this offset is that we don't need to calculate it using JS each time the sidebar is toggled.
65
+ * However, it could become out of sync if the width of IconButton changes.
66
+ *
67
+ * The alternative is using JS to store the previous position of the children wrapper element, and calculate the offset based on
68
+ * the new position, and then transforming using that offset. This would prevent the animation from going out of sync.
69
+ */
70
+ var childrenWrapperAnimationOffset = "calc(2rem + ".concat(flexGap, ")");
71
+ var childrenWrapperStyles = {
72
+ root: "_zulp1kw7 _1e0c1kw7 _1ul9idpf",
73
+ animationBaseStyles: "_10t81e03",
74
+ finalPosition: "_mjvcz12g _xrrp188d",
75
+ expandAnimationStartPosition: "_mjvcsws1",
76
+ collapseAnimationStartPosition: "_mjvc162w"
77
+ };
78
+
79
+ /**
80
+ * We use a fixed translateX offset for the toggle button slide animation (used when the TopNavStart children elements are reordered).
81
+ * The specific value doesn't matter too much for the toggle button animation, so we are using `100%`, which will match the toggle button width.
82
+ *
83
+ * By combining the `translateX` animation with an `opacity` fade, the feel and experience is actually quite similar to animating the
84
+ * element from the exact old position (offset), and it avoids the additional complexity of needing to track and calculate the exact offset.
85
+ */
86
+ var toggleButtonWrapperStyles = {
87
+ root: "_10t81rjc",
88
+ finalPosition: "_mjvcz12g _xrrp188d _bgpzkb7n",
89
+ expandAnimationStartPosition: "_mjvcjq3t _bgpzidpf",
90
+ collapseAnimationStartPosition: "_mjvcxwn4 _bgpzidpf",
91
+ expandAnimationTimingFunction: "_1xq51ytf",
92
+ collapseAnimationTimingFunction: "_1xq55ucs",
93
+ alignEnd: "_ahbq1wug"
94
+ };
95
+
46
96
  /**
47
97
  * The consistent key used for the side nav toggle button to ensure it does not get remounted
48
98
  * when it is reordered.
@@ -122,15 +172,77 @@ function TopNavStart(_ref3) {
122
172
  (0, _compiled.UNSAFE_useMediaQuery)('above.md', function (event) {
123
173
  setIsDesktop(event.matches);
124
174
  });
175
+ var _useState3 = (0, _react.useState)({
176
+ type: 'idle'
177
+ }),
178
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
179
+ animationState = _useState4[0],
180
+ setAnimationState = _useState4[1];
181
+
182
+ // Used to prevent the reorder animations from running on the initial render.
183
+ var isFirstRenderRef = (0, _react.useRef)(true);
184
+ (0, _react.useEffect)(function () {
185
+ if (!(0, _platformFeatureFlags.fg)('navx-full-height-sidebar')) {
186
+ return;
187
+ }
188
+ if (isFirstRenderRef.current) {
189
+ isFirstRenderRef.current = false;
190
+ }
191
+ }, []);
192
+
193
+ // Using a stable ref to avoid re-running the animation layout effect when the toggle button prop value changes, which
194
+ // can happen a lot (e.g. if the parent re-renders)
195
+ var sideNavToggleButtonStableRef = (0, _useStableRef.default)(sideNavToggleButton);
196
+ (0, _react.useLayoutEffect)(function () {
197
+ if (!(0, _platformFeatureFlags.fg)('navx-full-height-sidebar')) {
198
+ return;
199
+ }
200
+
201
+ /**
202
+ * This layout effect is used to animate the TopNavStart children elements to their new position after being reordered.
203
+ * It is called when the sidebar's desktop expansion state changes.
204
+ *
205
+ * It works by first setting a translateX offset on the elements, used as the start position of the slide animation.
206
+ * - For the toggle button, it's a fixed offset. It's combined with an opacity, so the exact offset doesn't matter too much.
207
+ * - For the children wrapper (wrapping everything except the toggle button), an offset was chosen to make the animation
208
+ * start position the exact same as the element's old position. See comments for `childrenWrapperStyles` for more details.
209
+ *
210
+ * On the next frame, the translateX offset is cleared, triggering the animation to the new position.
211
+ */
212
+
213
+ if (isFirstRenderRef.current) {
214
+ // No animations on initial render.
215
+ return;
216
+ }
217
+ if (!sideNavToggleButtonStableRef.current) {
218
+ // If there is no toggle button, there should be no re-order animations.
219
+ return;
220
+ }
221
+
222
+ // Set the translateX offsets so elements are ready to animate to their actual new position after being reordered
223
+ setAnimationState({
224
+ type: isExpandedOnDesktop ? 'expand' : 'collapse'
225
+ });
226
+ requestAnimationFrame(function () {
227
+ // Clear translateX offsets on next frame to trigger animation to new position in a re-render
228
+ setAnimationState({
229
+ type: 'idle'
230
+ });
231
+ });
232
+
233
+ // This layout effect is called when the sidebar's desktop expansion state changes.
234
+ }, [isExpandedOnDesktop, sideNavToggleButtonStableRef]);
125
235
  var TopNavStartInner = (0, _platformFeatureFlags.fg)('navx-full-height-sidebar') ? TopNavStartInnerFHS : TopNavStartInnerOld;
126
236
  return /*#__PURE__*/_react.default.createElement(TopNavStartInner, {
127
237
  ref: elementRef,
128
238
  testId: testId
129
- }, !(0, _platformFeatureFlags.fg)('navx-full-height-sidebar') && /*#__PURE__*/_react.default.createElement(_toggleButtonProvider.SideNavToggleButtonSlotProvider, {
130
- key: sideNavToggleButtonKey
131
- }, sideNavToggleButton), sideNavToggleButton && (!isDesktop || !isExpandedOnDesktop) && (0, _platformFeatureFlags.fg)('navx-full-height-sidebar') && /*#__PURE__*/_react.default.createElement(_toggleButtonProvider.SideNavToggleButtonSlotProvider, {
132
- key: sideNavToggleButtonKey
133
- }, sideNavToggleButton), children, sideNavToggleButton && isDesktop && isExpandedOnDesktop && (0, _platformFeatureFlags.fg)('navx-full-height-sidebar') && /*#__PURE__*/_react.default.createElement(_toggleButtonProvider.SideNavToggleButtonSlotProvider, {
134
- key: sideNavToggleButtonKey
239
+ }, !(0, _platformFeatureFlags.fg)('navx-full-height-sidebar') && sideNavToggleButton, sideNavToggleButton && (!isDesktop || !isExpandedOnDesktop) && (0, _platformFeatureFlags.fg)('navx-full-height-sidebar') && /*#__PURE__*/_react.default.createElement("div", {
240
+ key: sideNavToggleButtonKey,
241
+ className: (0, _runtime.ax)([!isFirefox && toggleButtonWrapperStyles.root, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.finalPosition, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.collapseAnimationTimingFunction, !isFirefox && animationState.type === 'collapse' && toggleButtonWrapperStyles.collapseAnimationStartPosition])
242
+ }, sideNavToggleButton), (0, _platformFeatureFlags.fg)('navx-full-height-sidebar') ? /*#__PURE__*/_react.default.createElement("div", {
243
+ className: (0, _runtime.ax)([childrenWrapperStyles.root, !isFirefox && childrenWrapperStyles.animationBaseStyles, !isFirefox && animationState.type === 'idle' && childrenWrapperStyles.finalPosition, !isFirefox && animationState.type === 'expand' && childrenWrapperStyles.expandAnimationStartPosition, !isFirefox && animationState.type === 'collapse' && childrenWrapperStyles.collapseAnimationStartPosition])
244
+ }, children) : children, sideNavToggleButton && isDesktop && isExpandedOnDesktop && (0, _platformFeatureFlags.fg)('navx-full-height-sidebar') && /*#__PURE__*/_react.default.createElement("div", {
245
+ key: sideNavToggleButtonKey,
246
+ className: (0, _runtime.ax)([!isFirefox && toggleButtonWrapperStyles.root, toggleButtonWrapperStyles.alignEnd, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.finalPosition, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.expandAnimationTimingFunction, !isFirefox && animationState.type === 'expand' && toggleButtonWrapperStyles.expandAnimationStartPosition])
135
247
  }, sideNavToggleButton));
136
248
  }
@@ -16,9 +16,4 @@ export const SideNavToggleButtonElement = /*#__PURE__*/createContext(null);
16
16
  * A callback ref is needed because the side nav can be mounted before the elements in the top bar (e.g. if the element
17
17
  * is lazy loaded, which happens in Jira and Confluence), which would prevent the event listeners from being set up.
18
18
  */
19
- export const SideNavToggleButtonAttachRef = /*#__PURE__*/createContext(__noop);
20
-
21
- /**
22
- * Used to check if the SideNavToggleButton is rendered inside of its slot in `TopNavStart`.
23
- */
24
- export const SideNavToggleButtonSlotContext = /*#__PURE__*/createContext(false);
19
+ export const SideNavToggleButtonAttachRef = /*#__PURE__*/createContext(__noop);
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from 'react';
2
- import { SideNavToggleButtonAttachRef, SideNavToggleButtonElement, SideNavToggleButtonSlotContext } from './toggle-button-context';
2
+ import { SideNavToggleButtonAttachRef, SideNavToggleButtonElement } from './toggle-button-context';
3
3
 
4
4
  /**
5
5
  * Provider for the side nav toggle button contexts.
@@ -23,17 +23,4 @@ export const SideNavToggleButtonProvider = ({
23
23
  }, /*#__PURE__*/React.createElement(SideNavToggleButtonAttachRef.Provider, {
24
24
  value: setElement
25
25
  }, children));
26
- };
27
-
28
- /**
29
- * Provider for the side nav toggle button slot.
30
- *
31
- * This allows us to determine if the toggle button is rendered inside or outside of its slot.
32
- */
33
- export const SideNavToggleButtonSlotProvider = ({
34
- children
35
- }) => {
36
- return /*#__PURE__*/React.createElement(SideNavToggleButtonSlotContext.Provider, {
37
- value: true
38
- }, children);
39
26
  };
@@ -1,3 +1,2 @@
1
1
  ._1e0c1bgi{display:contents}
2
- ._lcxvglyw{pointer-events:none}
3
- @media (min-width:64rem){._3l1a1wug{margin-inline-start:auto}}
2
+ ._lcxvglyw{pointer-events:none}
@@ -7,7 +7,7 @@ import SidebarCollapseIcon from '@atlaskit/icon/core/sidebar-collapse';
7
7
  import SidebarExpandIcon from '@atlaskit/icon/core/sidebar-expand';
8
8
  import { fg } from '@atlaskit/platform-feature-flags';
9
9
  import { IconButton } from '../../top-nav-items/themed/migration';
10
- import { SideNavToggleButtonAttachRef, SideNavToggleButtonSlotContext } from './toggle-button-context';
10
+ import { SideNavToggleButtonAttachRef } from './toggle-button-context';
11
11
  import { useSideNavVisibility } from './use-side-nav-visibility';
12
12
  import { useToggleSideNav } from './use-toggle-side-nav';
13
13
  const toggleButtonTooltipOptions = {
@@ -19,12 +19,6 @@ const toggleButtonTooltipOptions = {
19
19
  // For duplicate "mouseenter" issue when changing icons (see below)
20
20
  const silentIconStyles = null;
21
21
 
22
- /**
23
- * Wrapper styles to align the toggle button to the end of `TopNavStart`
24
- * when FHS is expanded.
25
- */
26
- const fullHeightSidebarExpandedWrapperStyles = null;
27
-
28
22
  /**
29
23
  * __SideNavToggleButton__
30
24
  *
@@ -125,7 +119,7 @@ export const SideNavToggleButton = ({
125
119
  }
126
120
  return toggleButtonTooltipOptions;
127
121
  }, [shortcut]);
128
- const iconButton = /*#__PURE__*/React.createElement(IconButton, {
122
+ return /*#__PURE__*/React.createElement(IconButton, {
129
123
  appearance: "subtle",
130
124
  label: isSideNavExpanded ? collapseLabel : expandLabel,
131
125
  icon: icon,
@@ -136,15 +130,4 @@ export const SideNavToggleButton = ({
136
130
  ref: fg('platform_dst_nav4_side_nav_toggle_ref_fix') ? setElement : elementRef,
137
131
  tooltip: tooltipProps
138
132
  });
139
- const isInsideSlot = useContext(SideNavToggleButtonSlotContext);
140
-
141
- // Checking `isInsideSlot` in case an app isn't using the slot
142
- // We don't want to break existing non-slot usage with the left margin
143
- // This check can be removed in the future, after slot is required for a while.
144
- if (isInsideSlot && fg('navx-full-height-sidebar')) {
145
- return /*#__PURE__*/React.createElement("div", {
146
- className: ax([isSideNavExpandedOnDesktop && "_3l1a1wug"])
147
- }, iconButton);
148
- }
149
- return iconButton;
150
133
  };
@@ -1,9 +1,14 @@
1
1
 
2
2
  ._zulp1b66{gap:var(--ds-space-050,4px)}
3
- ._yyhykb7n{grid-column:1}._1e0c1txw{display:flex}
3
+ ._zulp1kw7{gap:inherit}
4
+ ._yyhykb7n{grid-column:1}._1e0c1kw7{display:inherit}
5
+ ._1e0c1txw{display:flex}
6
+ ._1ul9idpf{min-width:0}
4
7
  ._4cvr1h6o{align-items:center}
5
8
  ._4t3i1osq{height:100%}
9
+ ._ahbq1wug{margin-inline-start:auto}
6
10
  ._bozgutpp{padding-inline-start:var(--ds-space-150,9pt)}
7
11
  ._lcxv1wug{pointer-events:auto}
8
12
  ._vchhusvi{box-sizing:border-box}
13
+ @media (prefers-reduced-motion:no-preference){._10t81e03{transition-property:transform}._10t81rjc{transition-property:transform,opacity}._1xq51ytf{transition-timing-function:ease-in-out}._1xq55ucs{transition-timing-function:ease}._mjvc162w{transform:translateX(calc(-2rem + var(--ds-space-050, 4px)*-1))}._mjvcjq3t{transform:translateX(-100%)}._bgpzidpf{opacity:0}._mjvcsws1{transform:translateX(calc(2rem + var(--ds-space-050, 4px)))}._mjvcxwn4{transform:translateX(100%)}._mjvcz12g{transform:translateX(0)}._xrrp188d{transition-duration:.3s}._bgpzkb7n{opacity:1}}
9
14
  @media (min-width:64rem){._15rin7od{min-width:unset}._glte1osq{width:100%}._15rip2n4{min-width:330px}._glte1ris{width:max-content}._15ri1mjv{min-width:300px}._1gs5usvi{box-sizing:border-box}._glte93mn{width:var(--n_sNvlw,100%)}._exxmpxbi{padding-inline-end:var(--ds-space-200,1pc)}}
@@ -2,12 +2,24 @@
2
2
  import "./top-nav-start.compiled.css";
3
3
  import { ax, ix } from "@compiled/react/runtime";
4
4
  import React, { forwardRef, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
5
+ import useStableRef from '@atlaskit/ds-lib/use-stable-ref';
5
6
  import { fg } from '@atlaskit/platform-feature-flags';
6
7
  import { UNSAFE_useMediaQuery } from '@atlaskit/primitives/compiled';
7
8
  import { TopNavStartAttachRef } from '../../../context/top-nav-start/top-nav-start-context';
8
- import { SideNavToggleButtonSlotProvider } from '../side-nav/toggle-button-provider';
9
9
  import { useSideNavVisibility } from '../side-nav/use-side-nav-visibility';
10
10
 
11
+ /**
12
+ * Firefox does support these reorder animations, but only partially enabling layout animations would look odd.
13
+ *
14
+ * We are using JS to detect Firefox and disable animations, instead of using CSS, as Compiled currently does not merge duplicate
15
+ * CSS at-rules when at-rules are nested: https://github.com/atlassian-labs/compiled/blob/e04a325915e1d13010205089e4915de0e53bc2d4/packages/css/src/plugins/merge-duplicate-at-rules.ts#L5
16
+ * Avoiding nesting the `@supports` at-rule inside of `@media` means Compiled can remove duplicate styles from the generated CSS.
17
+ */
18
+ const isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
19
+
20
+ // Placed in a variable, as the value is used in the translateX value for the children wrapper animation.
21
+ const flexGap = "var(--ds-space-050, 4px)";
22
+
11
23
  /**
12
24
  * Styles for the TopNavStart element.
13
25
  *
@@ -30,10 +42,48 @@ const innerStyles = {
30
42
  const wrapperStyles = {
31
43
  root: "_vchhusvi",
32
44
  fullHeightSidebar: "_bozgutpp",
33
- fullHeightSidebarCollapsed: "_15rip2n4",
34
45
  fullHeightSidebarExpanded: "_glte93mn _exxmpxbi"
35
46
  };
36
47
 
48
+ /**
49
+ * We use a fixed translateX offset for the slide animation (used when the TopNavStart children elements are reordered).
50
+ * This fixed offset makes the elements appear to animate smoothly from the old to the new position.
51
+ * This offset is calculated based on:
52
+ * - 32px (2rem) width of the side nav toggle button (IconButton)
53
+ * - 4px gap ('space.050' token) of the flex container
54
+ *
55
+ * The benefit of hardcoding this offset is that we don't need to calculate it using JS each time the sidebar is toggled.
56
+ * However, it could become out of sync if the width of IconButton changes.
57
+ *
58
+ * The alternative is using JS to store the previous position of the children wrapper element, and calculate the offset based on
59
+ * the new position, and then transforming using that offset. This would prevent the animation from going out of sync.
60
+ */
61
+ const childrenWrapperAnimationOffset = `calc(2rem + ${flexGap})`;
62
+ const childrenWrapperStyles = {
63
+ root: "_zulp1kw7 _1e0c1kw7 _1ul9idpf",
64
+ animationBaseStyles: "_10t81e03",
65
+ finalPosition: "_mjvcz12g _xrrp188d",
66
+ expandAnimationStartPosition: "_mjvcsws1",
67
+ collapseAnimationStartPosition: "_mjvc162w"
68
+ };
69
+
70
+ /**
71
+ * We use a fixed translateX offset for the toggle button slide animation (used when the TopNavStart children elements are reordered).
72
+ * The specific value doesn't matter too much for the toggle button animation, so we are using `100%`, which will match the toggle button width.
73
+ *
74
+ * By combining the `translateX` animation with an `opacity` fade, the feel and experience is actually quite similar to animating the
75
+ * element from the exact old position (offset), and it avoids the additional complexity of needing to track and calculate the exact offset.
76
+ */
77
+ const toggleButtonWrapperStyles = {
78
+ root: "_10t81rjc",
79
+ finalPosition: "_mjvcz12g _xrrp188d _bgpzkb7n",
80
+ expandAnimationStartPosition: "_mjvcjq3t _bgpzidpf",
81
+ collapseAnimationStartPosition: "_mjvcxwn4 _bgpzidpf",
82
+ expandAnimationTimingFunction: "_1xq51ytf",
83
+ collapseAnimationTimingFunction: "_1xq55ucs",
84
+ alignEnd: "_ahbq1wug"
85
+ };
86
+
37
87
  /**
38
88
  * The consistent key used for the side nav toggle button to ensure it does not get remounted
39
89
  * when it is reordered.
@@ -115,15 +165,74 @@ export function TopNavStart({
115
165
  UNSAFE_useMediaQuery('above.md', event => {
116
166
  setIsDesktop(event.matches);
117
167
  });
168
+ const [animationState, setAnimationState] = useState({
169
+ type: 'idle'
170
+ });
171
+
172
+ // Used to prevent the reorder animations from running on the initial render.
173
+ const isFirstRenderRef = useRef(true);
174
+ useEffect(() => {
175
+ if (!fg('navx-full-height-sidebar')) {
176
+ return;
177
+ }
178
+ if (isFirstRenderRef.current) {
179
+ isFirstRenderRef.current = false;
180
+ }
181
+ }, []);
182
+
183
+ // Using a stable ref to avoid re-running the animation layout effect when the toggle button prop value changes, which
184
+ // can happen a lot (e.g. if the parent re-renders)
185
+ const sideNavToggleButtonStableRef = useStableRef(sideNavToggleButton);
186
+ useLayoutEffect(() => {
187
+ if (!fg('navx-full-height-sidebar')) {
188
+ return;
189
+ }
190
+
191
+ /**
192
+ * This layout effect is used to animate the TopNavStart children elements to their new position after being reordered.
193
+ * It is called when the sidebar's desktop expansion state changes.
194
+ *
195
+ * It works by first setting a translateX offset on the elements, used as the start position of the slide animation.
196
+ * - For the toggle button, it's a fixed offset. It's combined with an opacity, so the exact offset doesn't matter too much.
197
+ * - For the children wrapper (wrapping everything except the toggle button), an offset was chosen to make the animation
198
+ * start position the exact same as the element's old position. See comments for `childrenWrapperStyles` for more details.
199
+ *
200
+ * On the next frame, the translateX offset is cleared, triggering the animation to the new position.
201
+ */
202
+
203
+ if (isFirstRenderRef.current) {
204
+ // No animations on initial render.
205
+ return;
206
+ }
207
+ if (!sideNavToggleButtonStableRef.current) {
208
+ // If there is no toggle button, there should be no re-order animations.
209
+ return;
210
+ }
211
+
212
+ // Set the translateX offsets so elements are ready to animate to their actual new position after being reordered
213
+ setAnimationState({
214
+ type: isExpandedOnDesktop ? 'expand' : 'collapse'
215
+ });
216
+ requestAnimationFrame(() => {
217
+ // Clear translateX offsets on next frame to trigger animation to new position in a re-render
218
+ setAnimationState({
219
+ type: 'idle'
220
+ });
221
+ });
222
+
223
+ // This layout effect is called when the sidebar's desktop expansion state changes.
224
+ }, [isExpandedOnDesktop, sideNavToggleButtonStableRef]);
118
225
  const TopNavStartInner = fg('navx-full-height-sidebar') ? TopNavStartInnerFHS : TopNavStartInnerOld;
119
226
  return /*#__PURE__*/React.createElement(TopNavStartInner, {
120
227
  ref: elementRef,
121
228
  testId: testId
122
- }, !fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement(SideNavToggleButtonSlotProvider, {
123
- key: sideNavToggleButtonKey
124
- }, sideNavToggleButton), sideNavToggleButton && (!isDesktop || !isExpandedOnDesktop) && fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement(SideNavToggleButtonSlotProvider, {
125
- key: sideNavToggleButtonKey
126
- }, sideNavToggleButton), children, sideNavToggleButton && isDesktop && isExpandedOnDesktop && fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement(SideNavToggleButtonSlotProvider, {
127
- key: sideNavToggleButtonKey
229
+ }, !fg('navx-full-height-sidebar') && sideNavToggleButton, sideNavToggleButton && (!isDesktop || !isExpandedOnDesktop) && fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement("div", {
230
+ key: sideNavToggleButtonKey,
231
+ className: ax([!isFirefox && toggleButtonWrapperStyles.root, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.finalPosition, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.collapseAnimationTimingFunction, !isFirefox && animationState.type === 'collapse' && toggleButtonWrapperStyles.collapseAnimationStartPosition])
232
+ }, sideNavToggleButton), fg('navx-full-height-sidebar') ? /*#__PURE__*/React.createElement("div", {
233
+ className: ax([childrenWrapperStyles.root, !isFirefox && childrenWrapperStyles.animationBaseStyles, !isFirefox && animationState.type === 'idle' && childrenWrapperStyles.finalPosition, !isFirefox && animationState.type === 'expand' && childrenWrapperStyles.expandAnimationStartPosition, !isFirefox && animationState.type === 'collapse' && childrenWrapperStyles.collapseAnimationStartPosition])
234
+ }, children) : children, sideNavToggleButton && isDesktop && isExpandedOnDesktop && fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement("div", {
235
+ key: sideNavToggleButtonKey,
236
+ className: ax([!isFirefox && toggleButtonWrapperStyles.root, toggleButtonWrapperStyles.alignEnd, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.finalPosition, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.expandAnimationTimingFunction, !isFirefox && animationState.type === 'expand' && toggleButtonWrapperStyles.expandAnimationStartPosition])
128
237
  }, sideNavToggleButton));
129
238
  }
@@ -16,9 +16,4 @@ export var SideNavToggleButtonElement = /*#__PURE__*/createContext(null);
16
16
  * A callback ref is needed because the side nav can be mounted before the elements in the top bar (e.g. if the element
17
17
  * is lazy loaded, which happens in Jira and Confluence), which would prevent the event listeners from being set up.
18
18
  */
19
- export var SideNavToggleButtonAttachRef = /*#__PURE__*/createContext(__noop);
20
-
21
- /**
22
- * Used to check if the SideNavToggleButton is rendered inside of its slot in `TopNavStart`.
23
- */
24
- export var SideNavToggleButtonSlotContext = /*#__PURE__*/createContext(false);
19
+ export var SideNavToggleButtonAttachRef = /*#__PURE__*/createContext(__noop);
@@ -1,6 +1,6 @@
1
1
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
2
  import React, { useState } from 'react';
3
- import { SideNavToggleButtonAttachRef, SideNavToggleButtonElement, SideNavToggleButtonSlotContext } from './toggle-button-context';
3
+ import { SideNavToggleButtonAttachRef, SideNavToggleButtonElement } from './toggle-button-context';
4
4
 
5
5
  /**
6
6
  * Provider for the side nav toggle button contexts.
@@ -26,16 +26,4 @@ export var SideNavToggleButtonProvider = function SideNavToggleButtonProvider(_r
26
26
  }, /*#__PURE__*/React.createElement(SideNavToggleButtonAttachRef.Provider, {
27
27
  value: setElement
28
28
  }, children));
29
- };
30
-
31
- /**
32
- * Provider for the side nav toggle button slot.
33
- *
34
- * This allows us to determine if the toggle button is rendered inside or outside of its slot.
35
- */
36
- export var SideNavToggleButtonSlotProvider = function SideNavToggleButtonSlotProvider(_ref2) {
37
- var children = _ref2.children;
38
- return /*#__PURE__*/React.createElement(SideNavToggleButtonSlotContext.Provider, {
39
- value: true
40
- }, children);
41
29
  };
@@ -1,3 +1,2 @@
1
1
  ._1e0c1bgi{display:contents}
2
- ._lcxvglyw{pointer-events:none}
3
- @media (min-width:64rem){._3l1a1wug{margin-inline-start:auto}}
2
+ ._lcxvglyw{pointer-events:none}
@@ -11,7 +11,7 @@ import SidebarCollapseIcon from '@atlaskit/icon/core/sidebar-collapse';
11
11
  import SidebarExpandIcon from '@atlaskit/icon/core/sidebar-expand';
12
12
  import { fg } from '@atlaskit/platform-feature-flags';
13
13
  import { IconButton } from '../../top-nav-items/themed/migration';
14
- import { SideNavToggleButtonAttachRef, SideNavToggleButtonSlotContext } from './toggle-button-context';
14
+ import { SideNavToggleButtonAttachRef } from './toggle-button-context';
15
15
  import { useSideNavVisibility } from './use-side-nav-visibility';
16
16
  import { useToggleSideNav } from './use-toggle-side-nav';
17
17
  var toggleButtonTooltipOptions = {
@@ -23,12 +23,6 @@ var toggleButtonTooltipOptions = {
23
23
  // For duplicate "mouseenter" issue when changing icons (see below)
24
24
  var silentIconStyles = null;
25
25
 
26
- /**
27
- * Wrapper styles to align the toggle button to the end of `TopNavStart`
28
- * when FHS is expanded.
29
- */
30
- var fullHeightSidebarExpandedWrapperStyles = null;
31
-
32
26
  /**
33
27
  * __SideNavToggleButton__
34
28
  *
@@ -135,7 +129,7 @@ export var SideNavToggleButton = function SideNavToggleButton(_ref) {
135
129
  }
136
130
  return toggleButtonTooltipOptions;
137
131
  }, [shortcut]);
138
- var iconButton = /*#__PURE__*/React.createElement(IconButton, {
132
+ return /*#__PURE__*/React.createElement(IconButton, {
139
133
  appearance: "subtle",
140
134
  label: isSideNavExpanded ? collapseLabel : expandLabel,
141
135
  icon: icon,
@@ -146,15 +140,4 @@ export var SideNavToggleButton = function SideNavToggleButton(_ref) {
146
140
  ref: fg('platform_dst_nav4_side_nav_toggle_ref_fix') ? setElement : elementRef,
147
141
  tooltip: tooltipProps
148
142
  });
149
- var isInsideSlot = useContext(SideNavToggleButtonSlotContext);
150
-
151
- // Checking `isInsideSlot` in case an app isn't using the slot
152
- // We don't want to break existing non-slot usage with the left margin
153
- // This check can be removed in the future, after slot is required for a while.
154
- if (isInsideSlot && fg('navx-full-height-sidebar')) {
155
- return /*#__PURE__*/React.createElement("div", {
156
- className: ax([isSideNavExpandedOnDesktop && "_3l1a1wug"])
157
- }, iconButton);
158
- }
159
- return iconButton;
160
143
  };
@@ -1,9 +1,14 @@
1
1
 
2
2
  ._zulp1b66{gap:var(--ds-space-050,4px)}
3
- ._yyhykb7n{grid-column:1}._1e0c1txw{display:flex}
3
+ ._zulp1kw7{gap:inherit}
4
+ ._yyhykb7n{grid-column:1}._1e0c1kw7{display:inherit}
5
+ ._1e0c1txw{display:flex}
6
+ ._1ul9idpf{min-width:0}
4
7
  ._4cvr1h6o{align-items:center}
5
8
  ._4t3i1osq{height:100%}
9
+ ._ahbq1wug{margin-inline-start:auto}
6
10
  ._bozgutpp{padding-inline-start:var(--ds-space-150,9pt)}
7
11
  ._lcxv1wug{pointer-events:auto}
8
12
  ._vchhusvi{box-sizing:border-box}
13
+ @media (prefers-reduced-motion:no-preference){._10t81e03{transition-property:transform}._10t81rjc{transition-property:transform,opacity}._1xq51ytf{transition-timing-function:ease-in-out}._1xq55ucs{transition-timing-function:ease}._mjvc162w{transform:translateX(calc(-2rem + var(--ds-space-050, 4px)*-1))}._mjvcjq3t{transform:translateX(-100%)}._bgpzidpf{opacity:0}._mjvcsws1{transform:translateX(calc(2rem + var(--ds-space-050, 4px)))}._mjvcxwn4{transform:translateX(100%)}._mjvcz12g{transform:translateX(0)}._xrrp188d{transition-duration:.3s}._bgpzkb7n{opacity:1}}
9
14
  @media (min-width:64rem){._15rin7od{min-width:unset}._glte1osq{width:100%}._15rip2n4{min-width:330px}._glte1ris{width:max-content}._15ri1mjv{min-width:300px}._1gs5usvi{box-sizing:border-box}._glte93mn{width:var(--n_sNvlw,100%)}._exxmpxbi{padding-inline-end:var(--ds-space-200,1pc)}}
@@ -3,12 +3,24 @@ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import "./top-nav-start.compiled.css";
4
4
  import { ax, ix } from "@compiled/react/runtime";
5
5
  import React, { forwardRef, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
6
+ import useStableRef from '@atlaskit/ds-lib/use-stable-ref';
6
7
  import { fg } from '@atlaskit/platform-feature-flags';
7
8
  import { UNSAFE_useMediaQuery } from '@atlaskit/primitives/compiled';
8
9
  import { TopNavStartAttachRef } from '../../../context/top-nav-start/top-nav-start-context';
9
- import { SideNavToggleButtonSlotProvider } from '../side-nav/toggle-button-provider';
10
10
  import { useSideNavVisibility } from '../side-nav/use-side-nav-visibility';
11
11
 
12
+ /**
13
+ * Firefox does support these reorder animations, but only partially enabling layout animations would look odd.
14
+ *
15
+ * We are using JS to detect Firefox and disable animations, instead of using CSS, as Compiled currently does not merge duplicate
16
+ * CSS at-rules when at-rules are nested: https://github.com/atlassian-labs/compiled/blob/e04a325915e1d13010205089e4915de0e53bc2d4/packages/css/src/plugins/merge-duplicate-at-rules.ts#L5
17
+ * Avoiding nesting the `@supports` at-rule inside of `@media` means Compiled can remove duplicate styles from the generated CSS.
18
+ */
19
+ var isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
20
+
21
+ // Placed in a variable, as the value is used in the translateX value for the children wrapper animation.
22
+ var flexGap = "var(--ds-space-050, 4px)";
23
+
12
24
  /**
13
25
  * Styles for the TopNavStart element.
14
26
  *
@@ -31,10 +43,48 @@ var innerStyles = {
31
43
  var wrapperStyles = {
32
44
  root: "_vchhusvi",
33
45
  fullHeightSidebar: "_bozgutpp",
34
- fullHeightSidebarCollapsed: "_15rip2n4",
35
46
  fullHeightSidebarExpanded: "_glte93mn _exxmpxbi"
36
47
  };
37
48
 
49
+ /**
50
+ * We use a fixed translateX offset for the slide animation (used when the TopNavStart children elements are reordered).
51
+ * This fixed offset makes the elements appear to animate smoothly from the old to the new position.
52
+ * This offset is calculated based on:
53
+ * - 32px (2rem) width of the side nav toggle button (IconButton)
54
+ * - 4px gap ('space.050' token) of the flex container
55
+ *
56
+ * The benefit of hardcoding this offset is that we don't need to calculate it using JS each time the sidebar is toggled.
57
+ * However, it could become out of sync if the width of IconButton changes.
58
+ *
59
+ * The alternative is using JS to store the previous position of the children wrapper element, and calculate the offset based on
60
+ * the new position, and then transforming using that offset. This would prevent the animation from going out of sync.
61
+ */
62
+ var childrenWrapperAnimationOffset = "calc(2rem + ".concat(flexGap, ")");
63
+ var childrenWrapperStyles = {
64
+ root: "_zulp1kw7 _1e0c1kw7 _1ul9idpf",
65
+ animationBaseStyles: "_10t81e03",
66
+ finalPosition: "_mjvcz12g _xrrp188d",
67
+ expandAnimationStartPosition: "_mjvcsws1",
68
+ collapseAnimationStartPosition: "_mjvc162w"
69
+ };
70
+
71
+ /**
72
+ * We use a fixed translateX offset for the toggle button slide animation (used when the TopNavStart children elements are reordered).
73
+ * The specific value doesn't matter too much for the toggle button animation, so we are using `100%`, which will match the toggle button width.
74
+ *
75
+ * By combining the `translateX` animation with an `opacity` fade, the feel and experience is actually quite similar to animating the
76
+ * element from the exact old position (offset), and it avoids the additional complexity of needing to track and calculate the exact offset.
77
+ */
78
+ var toggleButtonWrapperStyles = {
79
+ root: "_10t81rjc",
80
+ finalPosition: "_mjvcz12g _xrrp188d _bgpzkb7n",
81
+ expandAnimationStartPosition: "_mjvcjq3t _bgpzidpf",
82
+ collapseAnimationStartPosition: "_mjvcxwn4 _bgpzidpf",
83
+ expandAnimationTimingFunction: "_1xq51ytf",
84
+ collapseAnimationTimingFunction: "_1xq55ucs",
85
+ alignEnd: "_ahbq1wug"
86
+ };
87
+
38
88
  /**
39
89
  * The consistent key used for the side nav toggle button to ensure it does not get remounted
40
90
  * when it is reordered.
@@ -114,15 +164,77 @@ export function TopNavStart(_ref3) {
114
164
  UNSAFE_useMediaQuery('above.md', function (event) {
115
165
  setIsDesktop(event.matches);
116
166
  });
167
+ var _useState3 = useState({
168
+ type: 'idle'
169
+ }),
170
+ _useState4 = _slicedToArray(_useState3, 2),
171
+ animationState = _useState4[0],
172
+ setAnimationState = _useState4[1];
173
+
174
+ // Used to prevent the reorder animations from running on the initial render.
175
+ var isFirstRenderRef = useRef(true);
176
+ useEffect(function () {
177
+ if (!fg('navx-full-height-sidebar')) {
178
+ return;
179
+ }
180
+ if (isFirstRenderRef.current) {
181
+ isFirstRenderRef.current = false;
182
+ }
183
+ }, []);
184
+
185
+ // Using a stable ref to avoid re-running the animation layout effect when the toggle button prop value changes, which
186
+ // can happen a lot (e.g. if the parent re-renders)
187
+ var sideNavToggleButtonStableRef = useStableRef(sideNavToggleButton);
188
+ useLayoutEffect(function () {
189
+ if (!fg('navx-full-height-sidebar')) {
190
+ return;
191
+ }
192
+
193
+ /**
194
+ * This layout effect is used to animate the TopNavStart children elements to their new position after being reordered.
195
+ * It is called when the sidebar's desktop expansion state changes.
196
+ *
197
+ * It works by first setting a translateX offset on the elements, used as the start position of the slide animation.
198
+ * - For the toggle button, it's a fixed offset. It's combined with an opacity, so the exact offset doesn't matter too much.
199
+ * - For the children wrapper (wrapping everything except the toggle button), an offset was chosen to make the animation
200
+ * start position the exact same as the element's old position. See comments for `childrenWrapperStyles` for more details.
201
+ *
202
+ * On the next frame, the translateX offset is cleared, triggering the animation to the new position.
203
+ */
204
+
205
+ if (isFirstRenderRef.current) {
206
+ // No animations on initial render.
207
+ return;
208
+ }
209
+ if (!sideNavToggleButtonStableRef.current) {
210
+ // If there is no toggle button, there should be no re-order animations.
211
+ return;
212
+ }
213
+
214
+ // Set the translateX offsets so elements are ready to animate to their actual new position after being reordered
215
+ setAnimationState({
216
+ type: isExpandedOnDesktop ? 'expand' : 'collapse'
217
+ });
218
+ requestAnimationFrame(function () {
219
+ // Clear translateX offsets on next frame to trigger animation to new position in a re-render
220
+ setAnimationState({
221
+ type: 'idle'
222
+ });
223
+ });
224
+
225
+ // This layout effect is called when the sidebar's desktop expansion state changes.
226
+ }, [isExpandedOnDesktop, sideNavToggleButtonStableRef]);
117
227
  var TopNavStartInner = fg('navx-full-height-sidebar') ? TopNavStartInnerFHS : TopNavStartInnerOld;
118
228
  return /*#__PURE__*/React.createElement(TopNavStartInner, {
119
229
  ref: elementRef,
120
230
  testId: testId
121
- }, !fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement(SideNavToggleButtonSlotProvider, {
122
- key: sideNavToggleButtonKey
123
- }, sideNavToggleButton), sideNavToggleButton && (!isDesktop || !isExpandedOnDesktop) && fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement(SideNavToggleButtonSlotProvider, {
124
- key: sideNavToggleButtonKey
125
- }, sideNavToggleButton), children, sideNavToggleButton && isDesktop && isExpandedOnDesktop && fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement(SideNavToggleButtonSlotProvider, {
126
- key: sideNavToggleButtonKey
231
+ }, !fg('navx-full-height-sidebar') && sideNavToggleButton, sideNavToggleButton && (!isDesktop || !isExpandedOnDesktop) && fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement("div", {
232
+ key: sideNavToggleButtonKey,
233
+ className: ax([!isFirefox && toggleButtonWrapperStyles.root, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.finalPosition, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.collapseAnimationTimingFunction, !isFirefox && animationState.type === 'collapse' && toggleButtonWrapperStyles.collapseAnimationStartPosition])
234
+ }, sideNavToggleButton), fg('navx-full-height-sidebar') ? /*#__PURE__*/React.createElement("div", {
235
+ className: ax([childrenWrapperStyles.root, !isFirefox && childrenWrapperStyles.animationBaseStyles, !isFirefox && animationState.type === 'idle' && childrenWrapperStyles.finalPosition, !isFirefox && animationState.type === 'expand' && childrenWrapperStyles.expandAnimationStartPosition, !isFirefox && animationState.type === 'collapse' && childrenWrapperStyles.collapseAnimationStartPosition])
236
+ }, children) : children, sideNavToggleButton && isDesktop && isExpandedOnDesktop && fg('navx-full-height-sidebar') && /*#__PURE__*/React.createElement("div", {
237
+ key: sideNavToggleButtonKey,
238
+ className: ax([!isFirefox && toggleButtonWrapperStyles.root, toggleButtonWrapperStyles.alignEnd, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.finalPosition, !isFirefox && animationState.type === 'idle' && toggleButtonWrapperStyles.expandAnimationTimingFunction, !isFirefox && animationState.type === 'expand' && toggleButtonWrapperStyles.expandAnimationStartPosition])
127
239
  }, sideNavToggleButton));
128
240
  }
@@ -14,7 +14,3 @@ export declare const SideNavToggleButtonElement: import("react").Context<HTMLBut
14
14
  * is lazy loaded, which happens in Jira and Confluence), which would prevent the event listeners from being set up.
15
15
  */
16
16
  export declare const SideNavToggleButtonAttachRef: import("react").Context<(newVal: HTMLButtonElement | null) => void>;
17
- /**
18
- * Used to check if the SideNavToggleButton is rendered inside of its slot in `TopNavStart`.
19
- */
20
- export declare const SideNavToggleButtonSlotContext: import("react").Context<boolean>;
@@ -15,11 +15,3 @@ import React from 'react';
15
15
  export declare const SideNavToggleButtonProvider: ({ children }: {
16
16
  children: React.ReactNode;
17
17
  }) => React.JSX.Element;
18
- /**
19
- * Provider for the side nav toggle button slot.
20
- *
21
- * This allows us to determine if the toggle button is rendered inside or outside of its slot.
22
- */
23
- export declare const SideNavToggleButtonSlotProvider: ({ children }: {
24
- children: React.ReactNode;
25
- }) => React.JSX.Element;
@@ -14,7 +14,3 @@ export declare const SideNavToggleButtonElement: import("react").Context<HTMLBut
14
14
  * is lazy loaded, which happens in Jira and Confluence), which would prevent the event listeners from being set up.
15
15
  */
16
16
  export declare const SideNavToggleButtonAttachRef: import("react").Context<(newVal: HTMLButtonElement | null) => void>;
17
- /**
18
- * Used to check if the SideNavToggleButton is rendered inside of its slot in `TopNavStart`.
19
- */
20
- export declare const SideNavToggleButtonSlotContext: import("react").Context<boolean>;
@@ -15,11 +15,3 @@ import React from 'react';
15
15
  export declare const SideNavToggleButtonProvider: ({ children }: {
16
16
  children: React.ReactNode;
17
17
  }) => React.JSX.Element;
18
- /**
19
- * Provider for the side nav toggle button slot.
20
- *
21
- * This allows us to determine if the toggle button is rendered inside or outside of its slot.
22
- */
23
- export declare const SideNavToggleButtonSlotProvider: ({ children }: {
24
- children: React.ReactNode;
25
- }) => React.JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/navigation-system",
3
- "version": "2.19.1",
3
+ "version": "2.20.0",
4
4
  "description": "The latest navigation system for Atlassian apps.",
5
5
  "repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",
6
6
  "author": "Atlassian Pty Ltd",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@atlaskit/analytics-next": "^11.1.0",
70
- "@atlaskit/avatar": "^25.3.0",
70
+ "@atlaskit/avatar": "^25.4.0",
71
71
  "@atlaskit/button": "^23.5.0",
72
72
  "@atlaskit/css": "^0.14.0",
73
73
  "@atlaskit/ds-lib": "^5.1.0",