@atlaskit/navigation-system 2.0.0 → 2.1.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 (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/ui/page-layout/aside.js +8 -1
  3. package/dist/cjs/ui/page-layout/panel-splitter/context.js +11 -2
  4. package/dist/cjs/ui/page-layout/panel-splitter/get-width.js +5 -0
  5. package/dist/cjs/ui/page-layout/panel-splitter/panel-splitter.js +8 -1
  6. package/dist/cjs/ui/page-layout/panel-splitter/side-nav-panel-splitter.js +47 -0
  7. package/dist/cjs/ui/page-layout/panel.js +9 -1
  8. package/dist/cjs/ui/page-layout/side-nav/side-nav.js +9 -1
  9. package/dist/cjs/ui/page-layout/use-safe-default-width.js +32 -0
  10. package/dist/es2019/ui/page-layout/aside.js +8 -1
  11. package/dist/es2019/ui/page-layout/panel-splitter/context.js +10 -1
  12. package/dist/es2019/ui/page-layout/panel-splitter/get-width.js +5 -0
  13. package/dist/es2019/ui/page-layout/panel-splitter/panel-splitter.js +9 -2
  14. package/dist/es2019/ui/page-layout/panel-splitter/side-nav-panel-splitter.js +38 -0
  15. package/dist/es2019/ui/page-layout/panel.js +9 -1
  16. package/dist/es2019/ui/page-layout/side-nav/side-nav.js +9 -1
  17. package/dist/es2019/ui/page-layout/use-safe-default-width.js +28 -0
  18. package/dist/esm/ui/page-layout/aside.js +8 -1
  19. package/dist/esm/ui/page-layout/panel-splitter/context.js +10 -1
  20. package/dist/esm/ui/page-layout/panel-splitter/get-width.js +5 -0
  21. package/dist/esm/ui/page-layout/panel-splitter/panel-splitter.js +9 -2
  22. package/dist/esm/ui/page-layout/panel-splitter/side-nav-panel-splitter.js +38 -0
  23. package/dist/esm/ui/page-layout/panel.js +9 -1
  24. package/dist/esm/ui/page-layout/side-nav/side-nav.js +9 -1
  25. package/dist/esm/ui/page-layout/use-safe-default-width.js +27 -0
  26. package/dist/types/ui/page-layout/aside.d.ts +2 -2
  27. package/dist/types/ui/page-layout/panel-splitter/context.d.ts +8 -0
  28. package/dist/types/ui/page-layout/panel-splitter/panel-splitter.d.ts +1 -1
  29. package/dist/types/ui/page-layout/panel-splitter/side-nav-panel-splitter.d.ts +20 -0
  30. package/dist/types/ui/page-layout/panel.d.ts +2 -2
  31. package/dist/types/ui/page-layout/side-nav/side-nav.d.ts +2 -1
  32. package/dist/types/ui/page-layout/use-safe-default-width.d.ts +21 -0
  33. package/dist/types-ts4.5/ui/page-layout/aside.d.ts +2 -2
  34. package/dist/types-ts4.5/ui/page-layout/panel-splitter/context.d.ts +8 -0
  35. package/dist/types-ts4.5/ui/page-layout/panel-splitter/panel-splitter.d.ts +1 -1
  36. package/dist/types-ts4.5/ui/page-layout/panel-splitter/side-nav-panel-splitter.d.ts +20 -0
  37. package/dist/types-ts4.5/ui/page-layout/panel.d.ts +2 -2
  38. package/dist/types-ts4.5/ui/page-layout/side-nav/side-nav.d.ts +2 -1
  39. package/dist/types-ts4.5/ui/page-layout/use-safe-default-width.d.ts +21 -0
  40. package/package.json +5 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atlassian/navigation-system
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`8f30ac4ceb737`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8f30ac4ceb737) -
8
+ Adds extra guards for resizable slots to avoid invalid widths being set. This change is behind the
9
+ `platform_dst_nav4_panel_splitter_guards` feature gate.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
3
15
  ## 2.0.0
4
16
 
5
17
  ### Major Changes
@@ -22,6 +22,7 @@ var _hoistUtils = require("./hoist-utils");
22
22
  var _idUtils = require("./id-utils");
23
23
  var _provider = require("./panel-splitter/provider");
24
24
  var _useResizingWidthCssVarOnRootElement = require("./use-resizing-width-css-var-on-root-element");
25
+ var _useSafeDefaultWidth = require("./use-safe-default-width");
25
26
  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); }
26
27
  var panelSplitterResizingVar = '--n_asdRsz';
27
28
  /**
@@ -54,6 +55,7 @@ var styles = {
54
55
  root: "_nd5lns35 _vchhusvi _kqswh2mm _glte1kzp _ndwch9n0",
55
56
  inner: "_1reo1wug _18m91wug _152timx3 _4t3i1osq _165teqxy _13wn1if8"
56
57
  };
58
+ var fallbackDefaultWidth = 330;
57
59
 
58
60
  /**
59
61
  * The Aside is rendered to the right (inline end) of the Main area.
@@ -64,7 +66,7 @@ function Aside(_ref) {
64
66
  var children = _ref.children,
65
67
  xcss = _ref.xcss,
66
68
  _ref$defaultWidth = _ref.defaultWidth,
67
- defaultWidth = _ref$defaultWidth === void 0 ? 330 : _ref$defaultWidth,
69
+ defaultWidthProp = _ref$defaultWidth === void 0 ? fallbackDefaultWidth : _ref$defaultWidth,
68
70
  _ref$label = _ref.label,
69
71
  label = _ref$label === void 0 ? 'Aside' : _ref$label,
70
72
  _ref$skipLinkLabel = _ref.skipLinkLabel,
@@ -75,6 +77,11 @@ function Aside(_ref) {
75
77
  var id = (0, _idUtils.useLayoutId)({
76
78
  providedId: providedId
77
79
  });
80
+ var defaultWidth = (0, _useSafeDefaultWidth.useSafeDefaultWidth)({
81
+ defaultWidthProp: defaultWidthProp,
82
+ fallbackDefaultWidth: fallbackDefaultWidth,
83
+ slotName: 'Aside'
84
+ });
78
85
 
79
86
  /**
80
87
  * Don't show the skip link if the slot has 0 width.
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.PanelSplitterContext = void 0;
6
+ exports.PanelSplitterContext = exports.OnDoubleClickContext = void 0;
7
7
  var _react = require("react");
8
8
  // Disabling the rule to allow for `Type` suffix, to differentiate from the Context object.
9
9
  // eslint-disable-next-line @repo/internal/react/consistent-types-definitions
@@ -11,4 +11,13 @@ var _react = require("react");
11
11
  /**
12
12
  * Context for the panel splitter. Only internally exported.
13
13
  */
14
- var PanelSplitterContext = exports.PanelSplitterContext = /*#__PURE__*/(0, _react.createContext)(null);
14
+ var PanelSplitterContext = exports.PanelSplitterContext = /*#__PURE__*/(0, _react.createContext)(null);
15
+
16
+ /**
17
+ * Context for the panel splitter's double click handler. Only internally exported.
18
+ *
19
+ * NOTE: This context is a temporary workaround to enable the `SideNavPanelSplitter` component
20
+ * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
21
+ * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context should be removed.
22
+ */
23
+ var OnDoubleClickContext = exports.OnDoubleClickContext = /*#__PURE__*/(0, _react.createContext)(undefined);
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getWidthFromDragLocation = exports.getPixelWidth = void 0;
7
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
7
8
  var getWidthFromDragLocation = exports.getWidthFromDragLocation = function getWidthFromDragLocation(_ref) {
8
9
  var initialWidth = _ref.initialWidth,
9
10
  location = _ref.location,
@@ -25,6 +26,10 @@ var getWidthFromDragLocation = exports.getWidthFromDragLocation = function getWi
25
26
  * Returns the computed width of an element in pixels.
26
27
  */
27
28
  var getPixelWidth = exports.getPixelWidth = function getPixelWidth(element) {
29
+ if ((0, _platformFeatureFlags.fg)('platform_dst_nav4_panel_splitter_guards')) {
30
+ // Always returns an integer. Returns 0 if element is hidden / removed.
31
+ return element.offsetWidth;
32
+ }
28
33
  var _window$getComputedSt = window.getComputedStyle(element),
29
34
  width = _window$getComputedSt.width;
30
35
  return parseInt(width);
@@ -92,6 +92,13 @@ var PortaledPanelSplitter = function PortaledPanelSplitter(_ref) {
92
92
  rangeInputBounds = _useState4[0],
93
93
  setRangeInputBounds = _useState4[1];
94
94
  var openLayerObserver = (0, _openLayerObserver.useOpenLayerObserver)();
95
+
96
+ /**
97
+ * This is a temporary workaround to enable the `SideNavPanelSplitter` component
98
+ * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
99
+ * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context can be removed.
100
+ */
101
+ var onDoubleClick = (0, _react.useContext)(_context.OnDoubleClickContext);
95
102
  (0, _react.useEffect)(function () {
96
103
  var splitter = splitterRef.current;
97
104
  (0, _tinyInvariant.default)(splitter, 'Splitter ref must be set');
@@ -164,7 +171,6 @@ var PortaledPanelSplitter = function PortaledPanelSplitter(_ref) {
164
171
  var source = _ref5.source;
165
172
  (0, _tinyInvariant.default)(isPanelSplitterDragData(source.data));
166
173
  _preventUnhandled.preventUnhandled.stop();
167
- (0, _tinyInvariant.default)(isPanelSplitterDragData(source.data));
168
174
  var finalWidth = (0, _getWidth.getPixelWidth)(panel);
169
175
  onCompleteResize(finalWidth);
170
176
  onResizeEnd === null || onResizeEnd === void 0 || onResizeEnd({
@@ -276,6 +282,7 @@ var PortaledPanelSplitter = function PortaledPanelSplitter(_ref) {
276
282
  }, /*#__PURE__*/React.createElement("div", {
277
283
  ref: splitterRef,
278
284
  "data-testid": testId,
285
+ onDoubleClick: onDoubleClick,
279
286
  className: (0, _runtime.ax)([grabAreaStyles.root])
280
287
  }, /*#__PURE__*/React.createElement(_visuallyHidden.default, null, /*#__PURE__*/React.createElement("input", {
281
288
  type: "range",
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.SideNavPanelSplitter = void 0;
9
+ var _react = _interopRequireWildcard(require("react"));
10
+ var _tinyInvariant = _interopRequireDefault(require("tiny-invariant"));
11
+ var _constants = require("../constants");
12
+ var _useToggleSideNav = require("../side-nav/use-toggle-side-nav");
13
+ var _context = require("./context");
14
+ var _panelSplitter = require("./panel-splitter");
15
+ 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); }
16
+ /**
17
+ * _SideNavPanelSplitter_
18
+ *
19
+ * A component that allows the user to resize or collapse the side nav.
20
+ * It must be used within the `SideNav` layout area.
21
+ *
22
+ * Example usage:
23
+ * ```tsx
24
+ * <SideNav>
25
+ * <SideNavPanelSplitter label="Resize or collapse Side Nav" />
26
+ * </SideNav>
27
+ * ```
28
+ */
29
+ var SideNavPanelSplitter = exports.SideNavPanelSplitter = function SideNavPanelSplitter(_ref) {
30
+ var label = _ref.label,
31
+ onResizeStart = _ref.onResizeStart,
32
+ onResizeEnd = _ref.onResizeEnd,
33
+ testId = _ref.testId,
34
+ _ref$shouldCollapseOn = _ref.shouldCollapseOnDoubleClick,
35
+ shouldCollapseOnDoubleClick = _ref$shouldCollapseOn === void 0 ? true : _ref$shouldCollapseOn;
36
+ var context = (0, _react.useContext)(_context.PanelSplitterContext);
37
+ (0, _tinyInvariant.default)((context === null || context === void 0 ? void 0 : context.panelId) === _constants.sideNavPanelSplitterId, 'SideNavPanelSplitter must be rendered as a child of <SideNav />.');
38
+ var toggleSideNav = (0, _useToggleSideNav.useToggleSideNav)();
39
+ return /*#__PURE__*/_react.default.createElement(_context.OnDoubleClickContext.Provider, {
40
+ value: shouldCollapseOnDoubleClick ? toggleSideNav : undefined
41
+ }, /*#__PURE__*/_react.default.createElement(_panelSplitter.PanelSplitter, {
42
+ label: label,
43
+ onResizeStart: onResizeStart,
44
+ onResizeEnd: onResizeEnd,
45
+ testId: testId
46
+ }));
47
+ };
@@ -23,6 +23,7 @@ var _idUtils = require("./id-utils");
23
23
  var _provider = require("./panel-splitter/provider");
24
24
  var _elementContext = require("./side-nav/element-context");
25
25
  var _useResizingWidthCssVarOnRootElement = require("./use-resizing-width-css-var-on-root-element");
26
+ var _useSafeDefaultWidth = require("./use-safe-default-width");
26
27
  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); }
27
28
  var panelSplitterResizingVar = '--n_pnlRsz';
28
29
 
@@ -41,6 +42,7 @@ var styles = {
41
42
  oldMobileWidth: "_1bsb1adv",
42
43
  newMobileWidth: "_1bsb1dxx"
43
44
  };
45
+ var fallbackDefaultWidth = 365;
44
46
 
45
47
  /**
46
48
  * The Panel layout area is rendered to the right (inline end) of the Main area, or the Aside area if it is present.
@@ -52,7 +54,7 @@ var styles = {
52
54
  function Panel(_ref) {
53
55
  var children = _ref.children,
54
56
  _ref$defaultWidth = _ref.defaultWidth,
55
- defaultWidth = _ref$defaultWidth === void 0 ? 365 : _ref$defaultWidth,
57
+ defaultWidthProp = _ref$defaultWidth === void 0 ? fallbackDefaultWidth : _ref$defaultWidth,
56
58
  _ref$label = _ref.label,
57
59
  label = _ref$label === void 0 ? 'Panel' : _ref$label,
58
60
  _ref$skipLinkLabel = _ref.skipLinkLabel,
@@ -66,6 +68,12 @@ function Panel(_ref) {
66
68
  var id = (0, _idUtils.useLayoutId)({
67
69
  providedId: providedId
68
70
  });
71
+ var defaultWidth = (0, _useSafeDefaultWidth.useSafeDefaultWidth)({
72
+ defaultWidthProp: defaultWidthProp,
73
+ fallbackDefaultWidth: fallbackDefaultWidth,
74
+ slotName: 'Panel'
75
+ });
76
+
69
77
  /**
70
78
  * Don't show the skip link if the slot has 0 width.
71
79
  *
@@ -29,6 +29,7 @@ var _hoistUtils = require("../hoist-utils");
29
29
  var _idUtils = require("../id-utils");
30
30
  var _provider = require("../panel-splitter/provider");
31
31
  var _useResizingWidthCssVarOnRootElement = require("../use-resizing-width-css-var-on-root-element");
32
+ var _useSafeDefaultWidth = require("../use-safe-default-width");
32
33
  var _elementContext = require("./element-context");
33
34
  var _flyoutCloseDelayMs = require("./flyout-close-delay-ms");
34
35
  var _toggleButtonContext = require("./toggle-button-context");
@@ -59,6 +60,8 @@ var styles = {
59
60
  hiddenMobileOnly: "_1e0cglyw _dm2518uv",
60
61
  hiddenDesktopOnly: "_dm25glyw"
61
62
  };
63
+ var fallbackDefaultWidth = 320;
64
+
62
65
  /**
63
66
  * We need an additional component layer so we can wrap the side nav in a `OpenLayerObserver` and have access to the
64
67
  * context value.
@@ -67,7 +70,7 @@ function SideNavInternal(_ref) {
67
70
  var children = _ref.children,
68
71
  defaultCollapsed = _ref.defaultCollapsed,
69
72
  _ref$defaultWidth = _ref.defaultWidth,
70
- defaultWidth = _ref$defaultWidth === void 0 ? 320 : _ref$defaultWidth,
73
+ defaultWidthProp = _ref$defaultWidth === void 0 ? fallbackDefaultWidth : _ref$defaultWidth,
71
74
  testId = _ref.testId,
72
75
  _ref$label = _ref.label,
73
76
  label = _ref$label === void 0 ? 'Sidebar' : _ref$label,
@@ -113,6 +116,11 @@ function SideNavInternal(_ref) {
113
116
  var _useState = (0, _react.useState)(defaultCollapsed),
114
117
  _useState2 = (0, _slicedToArray2.default)(_useState, 1),
115
118
  initialDefaultCollapsed = _useState2[0];
119
+ var defaultWidth = (0, _useSafeDefaultWidth.useSafeDefaultWidth)({
120
+ defaultWidthProp: defaultWidthProp,
121
+ fallbackDefaultWidth: fallbackDefaultWidth,
122
+ slotName: 'SideNav'
123
+ });
116
124
  var _useState3 = (0, _react.useState)(defaultWidth),
117
125
  _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
118
126
  width = _useState4[0],
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useSafeDefaultWidth = useSafeDefaultWidth;
7
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
8
+ /**
9
+ * When `platform_dst_nav4_panel_splitter_guards` is disabled,
10
+ * `useSafeDefaultWidth` returns the provided `defaultWidthProp`.
11
+ *
12
+ * When `platform_dst_nav4_panel_splitter_guards` is enabled,
13
+ * `useSafeDefaultWidth` returns the `fallbackWidth` if the provided `defaultWidthProp` is not an integer value.
14
+ */
15
+ function useSafeDefaultWidth(_ref) {
16
+ var defaultWidthProp = _ref.defaultWidthProp,
17
+ fallbackDefaultWidth = _ref.fallbackDefaultWidth,
18
+ slotName = _ref.slotName;
19
+ if ((0, _platformFeatureFlags.fg)('platform_dst_nav4_panel_splitter_guards')) {
20
+ // If the provided `defaultWidth` is invalid then we use our fallback.
21
+ // We are using a runtime check because some invalid numbers like `NaN` are not caught by types,
22
+ // and we saw some issues in products where our experience broke due to this.
23
+ if (!Number.isInteger(defaultWidthProp)) {
24
+ if (process.env.NODE_ENV !== 'production') {
25
+ // eslint-disable-next-line no-console
26
+ console.error("The defaultWidth value must be an integer, but '".concat(defaultWidthProp, "' was provided to ").concat(slotName, ". Falling back to ").concat(fallbackDefaultWidth, "px instead."));
27
+ }
28
+ return fallbackDefaultWidth;
29
+ }
30
+ }
31
+ return defaultWidthProp;
32
+ }
@@ -12,6 +12,7 @@ import { DangerouslyHoistCssVarToDocumentRoot } from './hoist-utils';
12
12
  import { useLayoutId } from './id-utils';
13
13
  import { PanelSplitterProvider } from './panel-splitter/provider';
14
14
  import { useResizingWidthCssVarOnRootElement } from './use-resizing-width-css-var-on-root-element';
15
+ import { useSafeDefaultWidth } from './use-safe-default-width';
15
16
  const panelSplitterResizingVar = '--n_asdRsz';
16
17
  /**
17
18
  * The bounds for Aside and Panel are purposely set to support the current usage in Jira.
@@ -43,6 +44,7 @@ const styles = {
43
44
  root: "_nd5lns35 _vchhusvi _kqswh2mm _glte1kzp _ndwch9n0",
44
45
  inner: "_1reo1wug _18m91wug _152timx3 _4t3i1osq _165teqxy _13wn1if8"
45
46
  };
47
+ const fallbackDefaultWidth = 330;
46
48
 
47
49
  /**
48
50
  * The Aside is rendered to the right (inline end) of the Main area.
@@ -52,7 +54,7 @@ const styles = {
52
54
  export function Aside({
53
55
  children,
54
56
  xcss,
55
- defaultWidth = 330,
57
+ defaultWidth: defaultWidthProp = fallbackDefaultWidth,
56
58
  label = 'Aside',
57
59
  skipLinkLabel = label,
58
60
  testId,
@@ -62,6 +64,11 @@ export function Aside({
62
64
  const id = useLayoutId({
63
65
  providedId
64
66
  });
67
+ const defaultWidth = useSafeDefaultWidth({
68
+ defaultWidthProp,
69
+ fallbackDefaultWidth,
70
+ slotName: 'Aside'
71
+ });
65
72
 
66
73
  /**
67
74
  * Don't show the skip link if the slot has 0 width.
@@ -6,4 +6,13 @@ import { createContext } from 'react';
6
6
  /**
7
7
  * Context for the panel splitter. Only internally exported.
8
8
  */
9
- export const PanelSplitterContext = /*#__PURE__*/createContext(null);
9
+ export const PanelSplitterContext = /*#__PURE__*/createContext(null);
10
+
11
+ /**
12
+ * Context for the panel splitter's double click handler. Only internally exported.
13
+ *
14
+ * NOTE: This context is a temporary workaround to enable the `SideNavPanelSplitter` component
15
+ * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
16
+ * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context should be removed.
17
+ */
18
+ export const OnDoubleClickContext = /*#__PURE__*/createContext(undefined);
@@ -1,3 +1,4 @@
1
+ import { fg } from '@atlaskit/platform-feature-flags';
1
2
  export const getWidthFromDragLocation = ({
2
3
  initialWidth,
3
4
  location,
@@ -20,6 +21,10 @@ export const getWidthFromDragLocation = ({
20
21
  * Returns the computed width of an element in pixels.
21
22
  */
22
23
  export const getPixelWidth = element => {
24
+ if (fg('platform_dst_nav4_panel_splitter_guards')) {
25
+ // Always returns an integer. Returns 0 if element is hidden / removed.
26
+ return element.offsetWidth;
27
+ }
23
28
  const {
24
29
  width
25
30
  } = window.getComputedStyle(element);
@@ -15,7 +15,7 @@ import { blockDraggingToIFrames } from '@atlaskit/pragmatic-drag-and-drop/elemen
15
15
  import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
16
16
  import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled';
17
17
  import VisuallyHidden from '@atlaskit/visually-hidden';
18
- import { PanelSplitterContext } from './context';
18
+ import { OnDoubleClickContext, PanelSplitterContext } from './context';
19
19
  import { convertResizeBoundToPixels } from './convert-resize-bound-to-pixels';
20
20
  import { getPercentageWithinPixelBounds } from './get-percentage-within-pixel-bounds';
21
21
  import { getPixelWidth, getWidthFromDragLocation } from './get-width';
@@ -77,6 +77,13 @@ const PortaledPanelSplitter = ({
77
77
  max: 500
78
78
  });
79
79
  const openLayerObserver = useOpenLayerObserver();
80
+
81
+ /**
82
+ * This is a temporary workaround to enable the `SideNavPanelSplitter` component
83
+ * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
84
+ * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context can be removed.
85
+ */
86
+ const onDoubleClick = useContext(OnDoubleClickContext);
80
87
  useEffect(() => {
81
88
  const splitter = splitterRef.current;
82
89
  invariant(splitter, 'Splitter ref must be set');
@@ -154,7 +161,6 @@ const PortaledPanelSplitter = ({
154
161
  }) {
155
162
  invariant(isPanelSplitterDragData(source.data));
156
163
  preventUnhandled.stop();
157
- invariant(isPanelSplitterDragData(source.data));
158
164
  const finalWidth = getPixelWidth(panel);
159
165
  onCompleteResize(finalWidth);
160
166
  onResizeEnd === null || onResizeEnd === void 0 ? void 0 : onResizeEnd({
@@ -254,6 +260,7 @@ const PortaledPanelSplitter = ({
254
260
  }, /*#__PURE__*/React.createElement("div", {
255
261
  ref: splitterRef,
256
262
  "data-testid": testId,
263
+ onDoubleClick: onDoubleClick,
257
264
  className: ax([grabAreaStyles.root])
258
265
  }, /*#__PURE__*/React.createElement(VisuallyHidden, null, /*#__PURE__*/React.createElement("input", {
259
266
  type: "range",
@@ -0,0 +1,38 @@
1
+ import React, { useContext } from 'react';
2
+ import invariant from 'tiny-invariant';
3
+ import { sideNavPanelSplitterId } from '../constants';
4
+ import { useToggleSideNav } from '../side-nav/use-toggle-side-nav';
5
+ import { OnDoubleClickContext, PanelSplitterContext } from './context';
6
+ import { PanelSplitter } from './panel-splitter';
7
+ /**
8
+ * _SideNavPanelSplitter_
9
+ *
10
+ * A component that allows the user to resize or collapse the side nav.
11
+ * It must be used within the `SideNav` layout area.
12
+ *
13
+ * Example usage:
14
+ * ```tsx
15
+ * <SideNav>
16
+ * <SideNavPanelSplitter label="Resize or collapse Side Nav" />
17
+ * </SideNav>
18
+ * ```
19
+ */
20
+ export const SideNavPanelSplitter = ({
21
+ label,
22
+ onResizeStart,
23
+ onResizeEnd,
24
+ testId,
25
+ shouldCollapseOnDoubleClick = true
26
+ }) => {
27
+ const context = useContext(PanelSplitterContext);
28
+ invariant((context === null || context === void 0 ? void 0 : context.panelId) === sideNavPanelSplitterId, 'SideNavPanelSplitter must be rendered as a child of <SideNav />.');
29
+ const toggleSideNav = useToggleSideNav();
30
+ return /*#__PURE__*/React.createElement(OnDoubleClickContext.Provider, {
31
+ value: shouldCollapseOnDoubleClick ? toggleSideNav : undefined
32
+ }, /*#__PURE__*/React.createElement(PanelSplitter, {
33
+ label: label,
34
+ onResizeStart: onResizeStart,
35
+ onResizeEnd: onResizeEnd,
36
+ testId: testId
37
+ }));
38
+ };
@@ -13,6 +13,7 @@ import { useLayoutId } from './id-utils';
13
13
  import { PanelSplitterProvider } from './panel-splitter/provider';
14
14
  import { useSideNavRef } from './side-nav/element-context';
15
15
  import { useResizingWidthCssVarOnRootElement } from './use-resizing-width-css-var-on-root-element';
16
+ import { useSafeDefaultWidth } from './use-safe-default-width';
16
17
  const panelSplitterResizingVar = '--n_pnlRsz';
17
18
 
18
19
  /**
@@ -30,6 +31,7 @@ const styles = {
30
31
  oldMobileWidth: "_1bsb1adv",
31
32
  newMobileWidth: "_1bsb1dxx"
32
33
  };
34
+ const fallbackDefaultWidth = 365;
33
35
 
34
36
  /**
35
37
  * The Panel layout area is rendered to the right (inline end) of the Main area, or the Aside area if it is present.
@@ -40,7 +42,7 @@ const styles = {
40
42
  */
41
43
  export function Panel({
42
44
  children,
43
- defaultWidth = 365,
45
+ defaultWidth: defaultWidthProp = fallbackDefaultWidth,
44
46
  label = 'Panel',
45
47
  skipLinkLabel = label,
46
48
  testId,
@@ -52,6 +54,12 @@ export function Panel({
52
54
  const id = useLayoutId({
53
55
  providedId
54
56
  });
57
+ const defaultWidth = useSafeDefaultWidth({
58
+ defaultWidthProp,
59
+ fallbackDefaultWidth,
60
+ slotName: 'Panel'
61
+ });
62
+
55
63
  /**
56
64
  * Don't show the skip link if the slot has 0 width.
57
65
  *
@@ -19,6 +19,7 @@ import { DangerouslyHoistCssVarToDocumentRoot } from '../hoist-utils';
19
19
  import { useLayoutId } from '../id-utils';
20
20
  import { PanelSplitterProvider } from '../panel-splitter/provider';
21
21
  import { useResizingWidthCssVarOnRootElement } from '../use-resizing-width-css-var-on-root-element';
22
+ import { useSafeDefaultWidth } from '../use-safe-default-width';
22
23
  import { useSideNavRef } from './element-context';
23
24
  import { sideNavFlyoutCloseDelayMs } from './flyout-close-delay-ms';
24
25
  import { SideNavToggleButtonElement } from './toggle-button-context';
@@ -48,6 +49,8 @@ const styles = {
48
49
  hiddenMobileOnly: "_1e0cglyw _dm2518uv",
49
50
  hiddenDesktopOnly: "_dm25glyw"
50
51
  };
52
+ const fallbackDefaultWidth = 320;
53
+
51
54
  /**
52
55
  * We need an additional component layer so we can wrap the side nav in a `OpenLayerObserver` and have access to the
53
56
  * context value.
@@ -55,7 +58,7 @@ const styles = {
55
58
  function SideNavInternal({
56
59
  children,
57
60
  defaultCollapsed,
58
- defaultWidth = 320,
61
+ defaultWidth: defaultWidthProp = fallbackDefaultWidth,
59
62
  testId,
60
63
  label = 'Sidebar',
61
64
  skipLinkLabel = label,
@@ -99,6 +102,11 @@ function SideNavInternal({
99
102
  // This is so we can use it in an effect _that only runs once_, after the initial render on the client,
100
103
  // to sync the side nav context (provided in `<Root>`) with the `defaultCollapsed` prop provided to `<SideNav>`.
101
104
  const [initialDefaultCollapsed] = useState(defaultCollapsed);
105
+ const defaultWidth = useSafeDefaultWidth({
106
+ defaultWidthProp,
107
+ fallbackDefaultWidth,
108
+ slotName: 'SideNav'
109
+ });
102
110
  const [width, setWidth] = useState(defaultWidth);
103
111
  const clampedWidth = `clamp(${widthResizeBounds.min}, ${width}px, ${widthResizeBounds.max})`;
104
112
  const dangerouslyHoistSlotSizes = useContext(DangerouslyHoistSlotSizes);
@@ -0,0 +1,28 @@
1
+ import { fg } from '@atlaskit/platform-feature-flags';
2
+
3
+ /**
4
+ * When `platform_dst_nav4_panel_splitter_guards` is disabled,
5
+ * `useSafeDefaultWidth` returns the provided `defaultWidthProp`.
6
+ *
7
+ * When `platform_dst_nav4_panel_splitter_guards` is enabled,
8
+ * `useSafeDefaultWidth` returns the `fallbackWidth` if the provided `defaultWidthProp` is not an integer value.
9
+ */
10
+ export function useSafeDefaultWidth({
11
+ defaultWidthProp,
12
+ fallbackDefaultWidth,
13
+ slotName
14
+ }) {
15
+ if (fg('platform_dst_nav4_panel_splitter_guards')) {
16
+ // If the provided `defaultWidth` is invalid then we use our fallback.
17
+ // We are using a runtime check because some invalid numbers like `NaN` are not caught by types,
18
+ // and we saw some issues in products where our experience broke due to this.
19
+ if (!Number.isInteger(defaultWidthProp)) {
20
+ if (process.env.NODE_ENV !== 'production') {
21
+ // eslint-disable-next-line no-console
22
+ console.error(`The defaultWidth value must be an integer, but '${defaultWidthProp}' was provided to ${slotName}. Falling back to ${fallbackDefaultWidth}px instead.`);
23
+ }
24
+ return fallbackDefaultWidth;
25
+ }
26
+ }
27
+ return defaultWidthProp;
28
+ }
@@ -14,6 +14,7 @@ import { DangerouslyHoistCssVarToDocumentRoot } from './hoist-utils';
14
14
  import { useLayoutId } from './id-utils';
15
15
  import { PanelSplitterProvider } from './panel-splitter/provider';
16
16
  import { useResizingWidthCssVarOnRootElement } from './use-resizing-width-css-var-on-root-element';
17
+ import { useSafeDefaultWidth } from './use-safe-default-width';
17
18
  var panelSplitterResizingVar = '--n_asdRsz';
18
19
  /**
19
20
  * The bounds for Aside and Panel are purposely set to support the current usage in Jira.
@@ -45,6 +46,7 @@ var styles = {
45
46
  root: "_nd5lns35 _vchhusvi _kqswh2mm _glte1kzp _ndwch9n0",
46
47
  inner: "_1reo1wug _18m91wug _152timx3 _4t3i1osq _165teqxy _13wn1if8"
47
48
  };
49
+ var fallbackDefaultWidth = 330;
48
50
 
49
51
  /**
50
52
  * The Aside is rendered to the right (inline end) of the Main area.
@@ -55,7 +57,7 @@ export function Aside(_ref) {
55
57
  var children = _ref.children,
56
58
  xcss = _ref.xcss,
57
59
  _ref$defaultWidth = _ref.defaultWidth,
58
- defaultWidth = _ref$defaultWidth === void 0 ? 330 : _ref$defaultWidth,
60
+ defaultWidthProp = _ref$defaultWidth === void 0 ? fallbackDefaultWidth : _ref$defaultWidth,
59
61
  _ref$label = _ref.label,
60
62
  label = _ref$label === void 0 ? 'Aside' : _ref$label,
61
63
  _ref$skipLinkLabel = _ref.skipLinkLabel,
@@ -66,6 +68,11 @@ export function Aside(_ref) {
66
68
  var id = useLayoutId({
67
69
  providedId: providedId
68
70
  });
71
+ var defaultWidth = useSafeDefaultWidth({
72
+ defaultWidthProp: defaultWidthProp,
73
+ fallbackDefaultWidth: fallbackDefaultWidth,
74
+ slotName: 'Aside'
75
+ });
69
76
 
70
77
  /**
71
78
  * Don't show the skip link if the slot has 0 width.
@@ -6,4 +6,13 @@ import { createContext } from 'react';
6
6
  /**
7
7
  * Context for the panel splitter. Only internally exported.
8
8
  */
9
- export var PanelSplitterContext = /*#__PURE__*/createContext(null);
9
+ export var PanelSplitterContext = /*#__PURE__*/createContext(null);
10
+
11
+ /**
12
+ * Context for the panel splitter's double click handler. Only internally exported.
13
+ *
14
+ * NOTE: This context is a temporary workaround to enable the `SideNavPanelSplitter` component
15
+ * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
16
+ * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context should be removed.
17
+ */
18
+ export var OnDoubleClickContext = /*#__PURE__*/createContext(undefined);
@@ -1,3 +1,4 @@
1
+ import { fg } from '@atlaskit/platform-feature-flags';
1
2
  export var getWidthFromDragLocation = function getWidthFromDragLocation(_ref) {
2
3
  var initialWidth = _ref.initialWidth,
3
4
  location = _ref.location,
@@ -19,6 +20,10 @@ export var getWidthFromDragLocation = function getWidthFromDragLocation(_ref) {
19
20
  * Returns the computed width of an element in pixels.
20
21
  */
21
22
  export var getPixelWidth = function getPixelWidth(element) {
23
+ if (fg('platform_dst_nav4_panel_splitter_guards')) {
24
+ // Always returns an integer. Returns 0 if element is hidden / removed.
25
+ return element.offsetWidth;
26
+ }
22
27
  var _window$getComputedSt = window.getComputedStyle(element),
23
28
  width = _window$getComputedSt.width;
24
29
  return parseInt(width);
@@ -19,7 +19,7 @@ import { blockDraggingToIFrames } from '@atlaskit/pragmatic-drag-and-drop/elemen
19
19
  import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
20
20
  import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled';
21
21
  import VisuallyHidden from '@atlaskit/visually-hidden';
22
- import { PanelSplitterContext } from './context';
22
+ import { OnDoubleClickContext, PanelSplitterContext } from './context';
23
23
  import { convertResizeBoundToPixels } from './convert-resize-bound-to-pixels';
24
24
  import { getPercentageWithinPixelBounds } from './get-percentage-within-pixel-bounds';
25
25
  import { getPixelWidth, getWidthFromDragLocation } from './get-width';
@@ -82,6 +82,13 @@ var PortaledPanelSplitter = function PortaledPanelSplitter(_ref) {
82
82
  rangeInputBounds = _useState4[0],
83
83
  setRangeInputBounds = _useState4[1];
84
84
  var openLayerObserver = useOpenLayerObserver();
85
+
86
+ /**
87
+ * This is a temporary workaround to enable the `SideNavPanelSplitter` component
88
+ * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
89
+ * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context can be removed.
90
+ */
91
+ var onDoubleClick = useContext(OnDoubleClickContext);
85
92
  useEffect(function () {
86
93
  var splitter = splitterRef.current;
87
94
  invariant(splitter, 'Splitter ref must be set');
@@ -154,7 +161,6 @@ var PortaledPanelSplitter = function PortaledPanelSplitter(_ref) {
154
161
  var source = _ref5.source;
155
162
  invariant(isPanelSplitterDragData(source.data));
156
163
  preventUnhandled.stop();
157
- invariant(isPanelSplitterDragData(source.data));
158
164
  var finalWidth = getPixelWidth(panel);
159
165
  onCompleteResize(finalWidth);
160
166
  onResizeEnd === null || onResizeEnd === void 0 || onResizeEnd({
@@ -266,6 +272,7 @@ var PortaledPanelSplitter = function PortaledPanelSplitter(_ref) {
266
272
  }, /*#__PURE__*/React.createElement("div", {
267
273
  ref: splitterRef,
268
274
  "data-testid": testId,
275
+ onDoubleClick: onDoubleClick,
269
276
  className: ax([grabAreaStyles.root])
270
277
  }, /*#__PURE__*/React.createElement(VisuallyHidden, null, /*#__PURE__*/React.createElement("input", {
271
278
  type: "range",
@@ -0,0 +1,38 @@
1
+ import React, { useContext } from 'react';
2
+ import invariant from 'tiny-invariant';
3
+ import { sideNavPanelSplitterId } from '../constants';
4
+ import { useToggleSideNav } from '../side-nav/use-toggle-side-nav';
5
+ import { OnDoubleClickContext, PanelSplitterContext } from './context';
6
+ import { PanelSplitter } from './panel-splitter';
7
+ /**
8
+ * _SideNavPanelSplitter_
9
+ *
10
+ * A component that allows the user to resize or collapse the side nav.
11
+ * It must be used within the `SideNav` layout area.
12
+ *
13
+ * Example usage:
14
+ * ```tsx
15
+ * <SideNav>
16
+ * <SideNavPanelSplitter label="Resize or collapse Side Nav" />
17
+ * </SideNav>
18
+ * ```
19
+ */
20
+ export var SideNavPanelSplitter = function SideNavPanelSplitter(_ref) {
21
+ var label = _ref.label,
22
+ onResizeStart = _ref.onResizeStart,
23
+ onResizeEnd = _ref.onResizeEnd,
24
+ testId = _ref.testId,
25
+ _ref$shouldCollapseOn = _ref.shouldCollapseOnDoubleClick,
26
+ shouldCollapseOnDoubleClick = _ref$shouldCollapseOn === void 0 ? true : _ref$shouldCollapseOn;
27
+ var context = useContext(PanelSplitterContext);
28
+ invariant((context === null || context === void 0 ? void 0 : context.panelId) === sideNavPanelSplitterId, 'SideNavPanelSplitter must be rendered as a child of <SideNav />.');
29
+ var toggleSideNav = useToggleSideNav();
30
+ return /*#__PURE__*/React.createElement(OnDoubleClickContext.Provider, {
31
+ value: shouldCollapseOnDoubleClick ? toggleSideNav : undefined
32
+ }, /*#__PURE__*/React.createElement(PanelSplitter, {
33
+ label: label,
34
+ onResizeStart: onResizeStart,
35
+ onResizeEnd: onResizeEnd,
36
+ testId: testId
37
+ }));
38
+ };
@@ -15,6 +15,7 @@ import { useLayoutId } from './id-utils';
15
15
  import { PanelSplitterProvider } from './panel-splitter/provider';
16
16
  import { useSideNavRef } from './side-nav/element-context';
17
17
  import { useResizingWidthCssVarOnRootElement } from './use-resizing-width-css-var-on-root-element';
18
+ import { useSafeDefaultWidth } from './use-safe-default-width';
18
19
  var panelSplitterResizingVar = '--n_pnlRsz';
19
20
 
20
21
  /**
@@ -32,6 +33,7 @@ var styles = {
32
33
  oldMobileWidth: "_1bsb1adv",
33
34
  newMobileWidth: "_1bsb1dxx"
34
35
  };
36
+ var fallbackDefaultWidth = 365;
35
37
 
36
38
  /**
37
39
  * The Panel layout area is rendered to the right (inline end) of the Main area, or the Aside area if it is present.
@@ -43,7 +45,7 @@ var styles = {
43
45
  export function Panel(_ref) {
44
46
  var children = _ref.children,
45
47
  _ref$defaultWidth = _ref.defaultWidth,
46
- defaultWidth = _ref$defaultWidth === void 0 ? 365 : _ref$defaultWidth,
48
+ defaultWidthProp = _ref$defaultWidth === void 0 ? fallbackDefaultWidth : _ref$defaultWidth,
47
49
  _ref$label = _ref.label,
48
50
  label = _ref$label === void 0 ? 'Panel' : _ref$label,
49
51
  _ref$skipLinkLabel = _ref.skipLinkLabel,
@@ -57,6 +59,12 @@ export function Panel(_ref) {
57
59
  var id = useLayoutId({
58
60
  providedId: providedId
59
61
  });
62
+ var defaultWidth = useSafeDefaultWidth({
63
+ defaultWidthProp: defaultWidthProp,
64
+ fallbackDefaultWidth: fallbackDefaultWidth,
65
+ slotName: 'Panel'
66
+ });
67
+
60
68
  /**
61
69
  * Don't show the skip link if the slot has 0 width.
62
70
  *
@@ -21,6 +21,7 @@ import { DangerouslyHoistCssVarToDocumentRoot } from '../hoist-utils';
21
21
  import { useLayoutId } from '../id-utils';
22
22
  import { PanelSplitterProvider } from '../panel-splitter/provider';
23
23
  import { useResizingWidthCssVarOnRootElement } from '../use-resizing-width-css-var-on-root-element';
24
+ import { useSafeDefaultWidth } from '../use-safe-default-width';
24
25
  import { useSideNavRef } from './element-context';
25
26
  import { sideNavFlyoutCloseDelayMs } from './flyout-close-delay-ms';
26
27
  import { SideNavToggleButtonElement } from './toggle-button-context';
@@ -50,6 +51,8 @@ var styles = {
50
51
  hiddenMobileOnly: "_1e0cglyw _dm2518uv",
51
52
  hiddenDesktopOnly: "_dm25glyw"
52
53
  };
54
+ var fallbackDefaultWidth = 320;
55
+
53
56
  /**
54
57
  * We need an additional component layer so we can wrap the side nav in a `OpenLayerObserver` and have access to the
55
58
  * context value.
@@ -58,7 +61,7 @@ function SideNavInternal(_ref) {
58
61
  var children = _ref.children,
59
62
  defaultCollapsed = _ref.defaultCollapsed,
60
63
  _ref$defaultWidth = _ref.defaultWidth,
61
- defaultWidth = _ref$defaultWidth === void 0 ? 320 : _ref$defaultWidth,
64
+ defaultWidthProp = _ref$defaultWidth === void 0 ? fallbackDefaultWidth : _ref$defaultWidth,
62
65
  testId = _ref.testId,
63
66
  _ref$label = _ref.label,
64
67
  label = _ref$label === void 0 ? 'Sidebar' : _ref$label,
@@ -104,6 +107,11 @@ function SideNavInternal(_ref) {
104
107
  var _useState = useState(defaultCollapsed),
105
108
  _useState2 = _slicedToArray(_useState, 1),
106
109
  initialDefaultCollapsed = _useState2[0];
110
+ var defaultWidth = useSafeDefaultWidth({
111
+ defaultWidthProp: defaultWidthProp,
112
+ fallbackDefaultWidth: fallbackDefaultWidth,
113
+ slotName: 'SideNav'
114
+ });
107
115
  var _useState3 = useState(defaultWidth),
108
116
  _useState4 = _slicedToArray(_useState3, 2),
109
117
  width = _useState4[0],
@@ -0,0 +1,27 @@
1
+ import { fg } from '@atlaskit/platform-feature-flags';
2
+
3
+ /**
4
+ * When `platform_dst_nav4_panel_splitter_guards` is disabled,
5
+ * `useSafeDefaultWidth` returns the provided `defaultWidthProp`.
6
+ *
7
+ * When `platform_dst_nav4_panel_splitter_guards` is enabled,
8
+ * `useSafeDefaultWidth` returns the `fallbackWidth` if the provided `defaultWidthProp` is not an integer value.
9
+ */
10
+ export function useSafeDefaultWidth(_ref) {
11
+ var defaultWidthProp = _ref.defaultWidthProp,
12
+ fallbackDefaultWidth = _ref.fallbackDefaultWidth,
13
+ slotName = _ref.slotName;
14
+ if (fg('platform_dst_nav4_panel_splitter_guards')) {
15
+ // If the provided `defaultWidth` is invalid then we use our fallback.
16
+ // We are using a runtime check because some invalid numbers like `NaN` are not caught by types,
17
+ // and we saw some issues in products where our experience broke due to this.
18
+ if (!Number.isInteger(defaultWidthProp)) {
19
+ if (process.env.NODE_ENV !== 'production') {
20
+ // eslint-disable-next-line no-console
21
+ console.error("The defaultWidth value must be an integer, but '".concat(defaultWidthProp, "' was provided to ").concat(slotName, ". Falling back to ").concat(fallbackDefaultWidth, "px instead."));
22
+ }
23
+ return fallbackDefaultWidth;
24
+ }
25
+ }
26
+ return defaultWidthProp;
27
+ }
@@ -5,7 +5,7 @@ import type { CommonSlotProps } from './types';
5
5
  *
6
6
  * You can optionally render a `PanelSplitter` as a child to make the aside area resizable.
7
7
  */
8
- export declare function Aside({ children, xcss, defaultWidth, label, skipLinkLabel, testId, id: providedId, }: CommonSlotProps & {
8
+ export declare function Aside({ children, xcss, defaultWidth: defaultWidthProp, label, skipLinkLabel, testId, id: providedId, }: CommonSlotProps & {
9
9
  /**
10
10
  * The content of the layout area.
11
11
  */
@@ -21,7 +21,7 @@ export declare function Aside({ children, xcss, defaultWidth, label, skipLinkLab
21
21
  /**
22
22
  * The default width of the layout area.
23
23
  *
24
- * It should be between the resize bounds - the minimum is 120px and the maximum is 50% of the viewport width.
24
+ * It should be an integer between the resize bounds - the minimum is 120px and the maximum is 50% of the viewport width.
25
25
  */
26
26
  defaultWidth?: number;
27
27
  }): JSX.Element;
@@ -52,3 +52,11 @@ export type PanelSplitterContextType = {
52
52
  * Context for the panel splitter. Only internally exported.
53
53
  */
54
54
  export declare const PanelSplitterContext: import("react").Context<PanelSplitterContextType | null>;
55
+ /**
56
+ * Context for the panel splitter's double click handler. Only internally exported.
57
+ *
58
+ * NOTE: This context is a temporary workaround to enable the `SideNavPanelSplitter` component
59
+ * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
60
+ * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context should be removed.
61
+ */
62
+ export declare const OnDoubleClickContext: import("react").Context<(() => void) | undefined>;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { type ReactNode } from 'react';
6
6
  import type { ResizeBounds, ResizeEndCallback, ResizeStartCallback } from './types';
7
- type PanelSplitterProps = {
7
+ export type PanelSplitterProps = {
8
8
  /**
9
9
  * The accessible label for the panel splitter. It is visually hidden, but is required for accessibility.
10
10
  */
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type PanelSplitterProps } from './panel-splitter';
3
+ type SideNavPanelSplitterProps = Omit<PanelSplitterProps, 'onDoubleClick'> & {
4
+ shouldCollapseOnDoubleClick?: boolean;
5
+ };
6
+ /**
7
+ * _SideNavPanelSplitter_
8
+ *
9
+ * A component that allows the user to resize or collapse the side nav.
10
+ * It must be used within the `SideNav` layout area.
11
+ *
12
+ * Example usage:
13
+ * ```tsx
14
+ * <SideNav>
15
+ * <SideNavPanelSplitter label="Resize or collapse Side Nav" />
16
+ * </SideNav>
17
+ * ```
18
+ */
19
+ export declare const SideNavPanelSplitter: ({ label, onResizeStart, onResizeEnd, testId, shouldCollapseOnDoubleClick, }: SideNavPanelSplitterProps) => ReactNode;
20
+ export {};
@@ -7,7 +7,7 @@ import type { CommonSlotProps } from './types';
7
7
  *
8
8
  * You can optionally render a `PanelSplitter` as a child to make the panel area resizable.
9
9
  */
10
- export declare function Panel({ children, defaultWidth, label, skipLinkLabel, testId, id: providedId, xcss, hasBorder, }: CommonSlotProps & {
10
+ export declare function Panel({ children, defaultWidth: defaultWidthProp, label, skipLinkLabel, testId, id: providedId, xcss, hasBorder, }: CommonSlotProps & {
11
11
  /**
12
12
  * The content of the layout area.
13
13
  */
@@ -19,7 +19,7 @@ export declare function Panel({ children, defaultWidth, label, skipLinkLabel, te
19
19
  /**
20
20
  * The default width of the layout area.
21
21
  *
22
- * It should be between the resize bounds - the minimum is 120px and the maximum is 50% of the viewport width.
22
+ * It should be an integer between the resize bounds - the minimum is 120px and the maximum is 50% of the viewport width.
23
23
  *
24
24
  * This value is also used as the minimum resizing width, except when the `defaultWidth` is greater then `400px`,
25
25
  * in which case `400px` will be used as the minimum resizing width instead.
@@ -26,7 +26,8 @@ type SideNavProps = CommonSlotProps & {
26
26
  defaultCollapsed?: boolean;
27
27
  /**
28
28
  * The default width of the side nav layout area.
29
- * It should be between the resize bounds - the minimum is 240px and the maximum is 50% of the viewport width.
29
+ *
30
+ * It should be an integer between the resize bounds - the minimum is 240px and the maximum is 50% of the viewport width.
30
31
  *
31
32
  * It is only used when the side nav is first mounted, but you should continuously update your
32
33
  * persisted state using the `onResizeEnd` callback of `PanelSplitter`, to ensure it is up to date
@@ -0,0 +1,21 @@
1
+ /**
2
+ * When `platform_dst_nav4_panel_splitter_guards` is disabled,
3
+ * `useSafeDefaultWidth` returns the provided `defaultWidthProp`.
4
+ *
5
+ * When `platform_dst_nav4_panel_splitter_guards` is enabled,
6
+ * `useSafeDefaultWidth` returns the `fallbackWidth` if the provided `defaultWidthProp` is not an integer value.
7
+ */
8
+ export declare function useSafeDefaultWidth({ defaultWidthProp, fallbackDefaultWidth, slotName, }: {
9
+ /**
10
+ * The `defaultWidth` passed in by the app.
11
+ */
12
+ defaultWidthProp: number;
13
+ /**
14
+ * The fallback value for `defaultWidth` if `defaultWidthProp` is invalid.
15
+ */
16
+ fallbackDefaultWidth: number;
17
+ /**
18
+ * The name of the slot, used for the dev-time console error.
19
+ */
20
+ slotName: string;
21
+ }): number;
@@ -5,7 +5,7 @@ import type { CommonSlotProps } from './types';
5
5
  *
6
6
  * You can optionally render a `PanelSplitter` as a child to make the aside area resizable.
7
7
  */
8
- export declare function Aside({ children, xcss, defaultWidth, label, skipLinkLabel, testId, id: providedId, }: CommonSlotProps & {
8
+ export declare function Aside({ children, xcss, defaultWidth: defaultWidthProp, label, skipLinkLabel, testId, id: providedId, }: CommonSlotProps & {
9
9
  /**
10
10
  * The content of the layout area.
11
11
  */
@@ -21,7 +21,7 @@ export declare function Aside({ children, xcss, defaultWidth, label, skipLinkLab
21
21
  /**
22
22
  * The default width of the layout area.
23
23
  *
24
- * It should be between the resize bounds - the minimum is 120px and the maximum is 50% of the viewport width.
24
+ * It should be an integer between the resize bounds - the minimum is 120px and the maximum is 50% of the viewport width.
25
25
  */
26
26
  defaultWidth?: number;
27
27
  }): JSX.Element;
@@ -52,3 +52,11 @@ export type PanelSplitterContextType = {
52
52
  * Context for the panel splitter. Only internally exported.
53
53
  */
54
54
  export declare const PanelSplitterContext: import("react").Context<PanelSplitterContextType | null>;
55
+ /**
56
+ * Context for the panel splitter's double click handler. Only internally exported.
57
+ *
58
+ * NOTE: This context is a temporary workaround to enable the `SideNavPanelSplitter` component
59
+ * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
60
+ * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context should be removed.
61
+ */
62
+ export declare const OnDoubleClickContext: import("react").Context<(() => void) | undefined>;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { type ReactNode } from 'react';
6
6
  import type { ResizeBounds, ResizeEndCallback, ResizeStartCallback } from './types';
7
- type PanelSplitterProps = {
7
+ export type PanelSplitterProps = {
8
8
  /**
9
9
  * The accessible label for the panel splitter. It is visually hidden, but is required for accessibility.
10
10
  */
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type PanelSplitterProps } from './panel-splitter';
3
+ type SideNavPanelSplitterProps = Omit<PanelSplitterProps, 'onDoubleClick'> & {
4
+ shouldCollapseOnDoubleClick?: boolean;
5
+ };
6
+ /**
7
+ * _SideNavPanelSplitter_
8
+ *
9
+ * A component that allows the user to resize or collapse the side nav.
10
+ * It must be used within the `SideNav` layout area.
11
+ *
12
+ * Example usage:
13
+ * ```tsx
14
+ * <SideNav>
15
+ * <SideNavPanelSplitter label="Resize or collapse Side Nav" />
16
+ * </SideNav>
17
+ * ```
18
+ */
19
+ export declare const SideNavPanelSplitter: ({ label, onResizeStart, onResizeEnd, testId, shouldCollapseOnDoubleClick, }: SideNavPanelSplitterProps) => ReactNode;
20
+ export {};
@@ -7,7 +7,7 @@ import type { CommonSlotProps } from './types';
7
7
  *
8
8
  * You can optionally render a `PanelSplitter` as a child to make the panel area resizable.
9
9
  */
10
- export declare function Panel({ children, defaultWidth, label, skipLinkLabel, testId, id: providedId, xcss, hasBorder, }: CommonSlotProps & {
10
+ export declare function Panel({ children, defaultWidth: defaultWidthProp, label, skipLinkLabel, testId, id: providedId, xcss, hasBorder, }: CommonSlotProps & {
11
11
  /**
12
12
  * The content of the layout area.
13
13
  */
@@ -19,7 +19,7 @@ export declare function Panel({ children, defaultWidth, label, skipLinkLabel, te
19
19
  /**
20
20
  * The default width of the layout area.
21
21
  *
22
- * It should be between the resize bounds - the minimum is 120px and the maximum is 50% of the viewport width.
22
+ * It should be an integer between the resize bounds - the minimum is 120px and the maximum is 50% of the viewport width.
23
23
  *
24
24
  * This value is also used as the minimum resizing width, except when the `defaultWidth` is greater then `400px`,
25
25
  * in which case `400px` will be used as the minimum resizing width instead.
@@ -26,7 +26,8 @@ type SideNavProps = CommonSlotProps & {
26
26
  defaultCollapsed?: boolean;
27
27
  /**
28
28
  * The default width of the side nav layout area.
29
- * It should be between the resize bounds - the minimum is 240px and the maximum is 50% of the viewport width.
29
+ *
30
+ * It should be an integer between the resize bounds - the minimum is 240px and the maximum is 50% of the viewport width.
30
31
  *
31
32
  * It is only used when the side nav is first mounted, but you should continuously update your
32
33
  * persisted state using the `onResizeEnd` callback of `PanelSplitter`, to ensure it is up to date
@@ -0,0 +1,21 @@
1
+ /**
2
+ * When `platform_dst_nav4_panel_splitter_guards` is disabled,
3
+ * `useSafeDefaultWidth` returns the provided `defaultWidthProp`.
4
+ *
5
+ * When `platform_dst_nav4_panel_splitter_guards` is enabled,
6
+ * `useSafeDefaultWidth` returns the `fallbackWidth` if the provided `defaultWidthProp` is not an integer value.
7
+ */
8
+ export declare function useSafeDefaultWidth({ defaultWidthProp, fallbackDefaultWidth, slotName, }: {
9
+ /**
10
+ * The `defaultWidth` passed in by the app.
11
+ */
12
+ defaultWidthProp: number;
13
+ /**
14
+ * The fallback value for `defaultWidth` if `defaultWidthProp` is invalid.
15
+ */
16
+ fallbackDefaultWidth: number;
17
+ /**
18
+ * The name of the slot, used for the dev-time console error.
19
+ */
20
+ slotName: string;
21
+ }): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/navigation-system",
3
- "version": "2.0.0",
3
+ "version": "2.1.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",
@@ -142,7 +142,7 @@
142
142
  "@atlaskit/banner": "^14.0.0",
143
143
  "@atlaskit/breadcrumbs": "^15.3.0",
144
144
  "@atlaskit/dropdown-menu": "^16.3.0",
145
- "@atlaskit/form": "^12.2.0",
145
+ "@atlaskit/form": "^12.4.0",
146
146
  "@atlaskit/heading": "^5.2.0",
147
147
  "@atlaskit/link": "^3.2.0",
148
148
  "@atlaskit/lozenge": "^13.0.0",
@@ -222,6 +222,9 @@
222
222
  },
223
223
  "platform_dst_nav4_flyoutmenuitem_render_to_parent": {
224
224
  "type": "boolean"
225
+ },
226
+ "platform_dst_nav4_panel_splitter_guards": {
227
+ "type": "boolean"
225
228
  }
226
229
  },
227
230
  "homepage": "https://atlassian.design/components/navigation-system"