@atlaskit/navigation-system 11.3.3 → 11.4.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 (97) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +20 -0
  3. package/constellation/layout/code.mdx +1 -1
  4. package/constellation/layout/examples.mdx +4 -16
  5. package/constellation/layout/images/layout-anatomy-dark.png +0 -0
  6. package/constellation/layout/images/layout-anatomy-light.png +0 -0
  7. package/constellation/layout/images/layout-do-dark.png +0 -0
  8. package/constellation/layout/images/layout-do-light.png +0 -0
  9. package/constellation/layout/images/layout-dont-dark.png +0 -0
  10. package/constellation/layout/images/layout-dont-light.png +0 -0
  11. package/constellation/layout/images/layout-example-dark.png +0 -0
  12. package/constellation/layout/images/layout-example-light.png +0 -0
  13. package/constellation/layout/images/layout-example-sideNav-dark.png +0 -0
  14. package/constellation/layout/images/layout-example-sideNav-light.png +0 -0
  15. package/constellation/layout/images/layout-example-topNav-1-dark.png +0 -0
  16. package/constellation/layout/images/layout-example-topNav-1-light.png +0 -0
  17. package/constellation/layout/usage.mdx +1 -2
  18. package/constellation/top-nav-items/examples.mdx +7 -13
  19. package/dist/cjs/entry-points/layout/panel-layout.js +19 -0
  20. package/dist/cjs/ui/page-layout/calculate-layout-area-states.js +4 -4
  21. package/dist/cjs/ui/page-layout/chat-panel.js +1 -1
  22. package/dist/cjs/ui/page-layout/constants.js +15 -1
  23. package/dist/cjs/ui/page-layout/get-panel-layout-defaults.js +22 -0
  24. package/dist/cjs/ui/page-layout/layout-area-sizing-provider.js +2 -1
  25. package/dist/cjs/ui/page-layout/layout-area-sizing-runtime.js +5 -4
  26. package/dist/cjs/ui/page-layout/local-panel-surface.compiled.css +26 -0
  27. package/dist/cjs/ui/page-layout/local-panel-surface.js +206 -0
  28. package/dist/cjs/ui/page-layout/main/main.js +5 -2
  29. package/dist/cjs/ui/page-layout/main-min-width-context.js +10 -0
  30. package/dist/cjs/ui/page-layout/panel-layout-shell.compiled.css +27 -0
  31. package/dist/cjs/ui/page-layout/panel-layout-shell.js +67 -0
  32. package/dist/cjs/ui/page-layout/panel-layout.js +162 -0
  33. package/dist/cjs/ui/page-layout/panel-splitter/panel-splitter.js +12 -5
  34. package/dist/cjs/ui/page-layout/root.js +3 -1
  35. package/dist/cjs/ui/page-layout/side-nav/bind-open-layer-change.js +56 -0
  36. package/dist/cjs/ui/page-layout/side-nav/side-nav.js +167 -57
  37. package/dist/cjs/ui/page-layout/side-nav/toggle-button.js +8 -1
  38. package/dist/cjs/ui/page-layout/side-nav/use-side-nav-toggle-keyboard-shortcut.js +15 -1
  39. package/dist/cjs/ui/page-layout/top-nav/top-nav-end.js +3 -2
  40. package/dist/cjs/ui/page-layout/top-nav/top-nav-middle.js +2 -1
  41. package/dist/es2019/entry-points/layout/panel-layout.js +2 -0
  42. package/dist/es2019/ui/page-layout/calculate-layout-area-states.js +4 -4
  43. package/dist/es2019/ui/page-layout/chat-panel.js +2 -2
  44. package/dist/es2019/ui/page-layout/constants.js +14 -0
  45. package/dist/es2019/ui/page-layout/get-panel-layout-defaults.js +16 -0
  46. package/dist/es2019/ui/page-layout/layout-area-sizing-provider.js +2 -1
  47. package/dist/es2019/ui/page-layout/layout-area-sizing-runtime.js +4 -3
  48. package/dist/es2019/ui/page-layout/local-panel-surface.compiled.css +26 -0
  49. package/dist/es2019/ui/page-layout/local-panel-surface.js +193 -0
  50. package/dist/es2019/ui/page-layout/main/main.js +6 -3
  51. package/dist/es2019/ui/page-layout/main-min-width-context.js +5 -0
  52. package/dist/es2019/ui/page-layout/panel-layout-shell.compiled.css +27 -0
  53. package/dist/es2019/ui/page-layout/panel-layout-shell.js +74 -0
  54. package/dist/es2019/ui/page-layout/panel-layout.js +147 -0
  55. package/dist/es2019/ui/page-layout/panel-splitter/panel-splitter.js +12 -5
  56. package/dist/es2019/ui/page-layout/root.js +3 -1
  57. package/dist/es2019/ui/page-layout/side-nav/bind-open-layer-change.js +51 -0
  58. package/dist/es2019/ui/page-layout/side-nav/side-nav.js +123 -10
  59. package/dist/es2019/ui/page-layout/side-nav/toggle-button.js +8 -1
  60. package/dist/es2019/ui/page-layout/side-nav/use-side-nav-toggle-keyboard-shortcut.js +15 -1
  61. package/dist/es2019/ui/page-layout/top-nav/top-nav-end.js +3 -2
  62. package/dist/es2019/ui/page-layout/top-nav/top-nav-middle.js +2 -1
  63. package/dist/esm/entry-points/layout/panel-layout.js +2 -0
  64. package/dist/esm/ui/page-layout/calculate-layout-area-states.js +4 -4
  65. package/dist/esm/ui/page-layout/chat-panel.js +2 -2
  66. package/dist/esm/ui/page-layout/constants.js +14 -0
  67. package/dist/esm/ui/page-layout/get-panel-layout-defaults.js +16 -0
  68. package/dist/esm/ui/page-layout/layout-area-sizing-provider.js +2 -1
  69. package/dist/esm/ui/page-layout/layout-area-sizing-runtime.js +4 -3
  70. package/dist/esm/ui/page-layout/local-panel-surface.compiled.css +26 -0
  71. package/dist/esm/ui/page-layout/local-panel-surface.js +197 -0
  72. package/dist/esm/ui/page-layout/main/main.js +6 -3
  73. package/dist/esm/ui/page-layout/main-min-width-context.js +5 -0
  74. package/dist/esm/ui/page-layout/panel-layout-shell.compiled.css +27 -0
  75. package/dist/esm/ui/page-layout/panel-layout-shell.js +59 -0
  76. package/dist/esm/ui/page-layout/panel-layout.js +154 -0
  77. package/dist/esm/ui/page-layout/panel-splitter/panel-splitter.js +12 -5
  78. package/dist/esm/ui/page-layout/root.js +3 -1
  79. package/dist/esm/ui/page-layout/side-nav/bind-open-layer-change.js +50 -0
  80. package/dist/esm/ui/page-layout/side-nav/side-nav.js +168 -58
  81. package/dist/esm/ui/page-layout/side-nav/toggle-button.js +8 -1
  82. package/dist/esm/ui/page-layout/side-nav/use-side-nav-toggle-keyboard-shortcut.js +15 -1
  83. package/dist/esm/ui/page-layout/top-nav/top-nav-end.js +3 -2
  84. package/dist/esm/ui/page-layout/top-nav/top-nav-middle.js +2 -1
  85. package/dist/types/entry-points/layout/panel-layout.d.ts +3 -0
  86. package/dist/types/ui/page-layout/constants.d.ts +12 -0
  87. package/dist/types/ui/page-layout/get-panel-layout-defaults.d.ts +10 -0
  88. package/dist/types/ui/page-layout/layout-area-sizing-context.d.ts +5 -1
  89. package/dist/types/ui/page-layout/local-panel-surface.d.ts +32 -0
  90. package/dist/types/ui/page-layout/main-min-width-context.d.ts +2 -0
  91. package/dist/types/ui/page-layout/panel-layout-shell.d.ts +47 -0
  92. package/dist/types/ui/page-layout/panel-layout.d.ts +74 -0
  93. package/dist/types/ui/page-layout/side-nav/bind-open-layer-change.d.ts +17 -0
  94. package/layout/panel-layout/package.json +10 -0
  95. package/package.json +6 -6
  96. package/constellation/layout/images/layout-example-topNav-2-dark.png +0 -0
  97. package/constellation/layout/images/layout-example-topNav-2-light.png +0 -0
@@ -0,0 +1,193 @@
1
+ /* local-panel-surface.tsx generated by @compiled/babel-plugin v3.0.2 */
2
+ import "./local-panel-surface.compiled.css";
3
+ import * as React from 'react';
4
+ import { ax, ix } from "@compiled/react/runtime";
5
+ import { useCallback, useContext, useMemo, useRef } from 'react';
6
+ import mergeRefs from '@atlaskit/ds-lib/merge-refs';
7
+ import { useLayoutEffect } from '@atlaskit/ds-lib/use-layout-effect';
8
+ import useStableRef from '@atlaskit/ds-lib/use-stable-ref';
9
+ import { useMotion } from '@atlaskit/motion/entering/use-motion';
10
+ import ExitingPersistence from '@atlaskit/motion/exiting-persistence';
11
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
12
+ import { localPanelWidthVar } from './constants';
13
+ import { DangerouslyHoistSlotSizes } from './hoist-slot-sizes-context';
14
+ import { PanelSplitter } from './panel-splitter/panel-splitter';
15
+ import { PanelSplitterProvider } from './panel-splitter/provider';
16
+ const localPanelVar = '--p_lclPnlW';
17
+ const panelSplitterResizingVar = '--p_lclPnlRsz';
18
+ const localPanelSplitterId = Symbol('LocalPanel PanelSplitter');
19
+
20
+ // Compiled preserves the existing container-relative widths and discrete motion styles.
21
+ /* eslint-disable @compiled/shorthand-property-sorting -- Content motion overrides only the animation delay. */
22
+ const styles = {
23
+ panelWrapper: "_nd5l1gzg _kqsw1if8 _bfhk1bhr _vchhusvi _4t3ieqxy _152tidpf _10fph9n0 _1bsb13q4",
24
+ managedInline: "_nd5l1c6w _bfhkvuon _152tidpf _1bsb1c5w",
25
+ unmeasured: "_nd5l32aw _191wbqna _1bsb1cku _bfhk1g1j _16qs1dhr",
26
+ managedOverlay: "_nd5l1gzg _bfhk1bhr _1bsb1c5w",
27
+ panelScrollContainer: "_1reo1wug _18m91wug _4t3i1osq",
28
+ border: "_191wglyw _16qs130s",
29
+ managedInlineBorder: "_191wia51 _16qsglyw",
30
+ entering: "_k1af1nn1 _10t81ia4 _xrrp147i _1wsa1ign _1vrh1a5r _1kmh1n9t _105abamb",
31
+ exiting: "_k1af14vv _bq641n9t _105aeydr",
32
+ contentEntering: "_y44v1mph",
33
+ contentExiting: "_y44vf1qi",
34
+ contentChangeContainer: "_4t3i1osq"
35
+ };
36
+ const contentEnteringDelayStyles = {
37
+ root: "_ju25dn4k"
38
+ };
39
+ /* eslint-enable @compiled/shorthand-property-sorting */
40
+
41
+ const PanelContentMotion = ({
42
+ children
43
+ }) => {
44
+ const {
45
+ state,
46
+ ref
47
+ } = useMotion();
48
+ return /*#__PURE__*/React.createElement("div", {
49
+ ref: ref,
50
+ className: ax([styles.contentChangeContainer, state === 'entering' && styles.contentEntering, state === 'exiting' && styles.contentExiting])
51
+ }, children);
52
+ };
53
+ /**
54
+ * Internal surface for PanelLayout. The host supplies content; navigation owns its geometry.
55
+ */
56
+ export function LocalPanelSurface({
57
+ children,
58
+ contentKey,
59
+ label,
60
+ resizeLabel,
61
+ defaultWidth,
62
+ minWidth,
63
+ maxWidth,
64
+ testId,
65
+ layoutRef,
66
+ managedSizing,
67
+ getManagedResizeBounds,
68
+ prepareResize,
69
+ onManagedResizeStart,
70
+ onManagedResize,
71
+ onManagedResizeEnd,
72
+ onWidthChange,
73
+ onResizeEnd
74
+ }) {
75
+ var _managedSizing$mode, _managedSizing$width;
76
+ const ref = useRef(null);
77
+ const dangerouslyHoistSlotSizes = useContext(DangerouslyHoistSlotSizes);
78
+ useLayoutEffect(() => {
79
+ const panel = ref.current;
80
+ if (!dangerouslyHoistSlotSizes || !panel) {
81
+ return;
82
+ }
83
+ // Container-relative CSS owns the initial width, before the sizing runtime loads.
84
+ // Publish geometry directly for external controls; never feed it into saved preferences.
85
+ let previousWidth;
86
+ const publishWidth = width => {
87
+ if (width !== previousWidth) {
88
+ previousWidth = width;
89
+ panel.ownerDocument.documentElement.style.setProperty(localPanelWidthVar, `${width}px`);
90
+ }
91
+ };
92
+ publishWidth(panel.getBoundingClientRect().width);
93
+ const observer = new ResizeObserver(([entry]) => {
94
+ var _entry$borderBoxSize$, _entry$borderBoxSize, _entry$borderBoxSize$2;
95
+ publishWidth((_entry$borderBoxSize$ = (_entry$borderBoxSize = entry.borderBoxSize) === null || _entry$borderBoxSize === void 0 ? void 0 : (_entry$borderBoxSize$2 = _entry$borderBoxSize[0]) === null || _entry$borderBoxSize$2 === void 0 ? void 0 : _entry$borderBoxSize$2.inlineSize) !== null && _entry$borderBoxSize$ !== void 0 ? _entry$borderBoxSize$ : panel.getBoundingClientRect().width);
96
+ });
97
+ observer.observe(panel, {
98
+ box: 'border-box'
99
+ });
100
+ return () => {
101
+ observer.disconnect();
102
+ panel.ownerDocument.documentElement.style.removeProperty(localPanelWidthVar);
103
+ };
104
+ }, [dangerouslyHoistSlotSizes]);
105
+ const scrollRef = useRef(null);
106
+ useLayoutEffect(() => {
107
+ if (scrollRef.current) {
108
+ scrollRef.current.scrollTop = 0;
109
+ }
110
+ }, [contentKey]);
111
+ const {
112
+ state,
113
+ ref: motionRef
114
+ } = useMotion();
115
+ const isMotionUpliftEnabled = fg('platform-dst-motion-uplift-panel');
116
+ const isContentMotionEnabled = fg('platform-dst-motion-uplift-panel-content');
117
+ const mergedRef = useMemo(() => isMotionUpliftEnabled ? mergeRefs([ref, motionRef]) : ref, [isMotionUpliftEnabled, motionRef]);
118
+ const mode = (_managedSizing$mode = managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) !== null && _managedSizing$mode !== void 0 ? _managedSizing$mode : 'overlay';
119
+ const managedMode = useStableRef(managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode);
120
+ // Resize preparation can load the coordinator after the interaction starts.
121
+ // Read its latest mode instead of the pre-load callback's captured fallback.
122
+ const getMode = useCallback(() => {
123
+ if (managedMode.current) {
124
+ return managedMode.current;
125
+ }
126
+ return ref.current && getComputedStyle(ref.current).gridArea.split('/')[0].trim() === 'panel' ? 'inline' : 'overlay';
127
+ }, [managedMode]);
128
+ const dragMode = useRef('overlay');
129
+ const completedResizeModeRef = useRef(mode);
130
+ const renderedWidth = (_managedSizing$width = managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.width) !== null && _managedSizing$width !== void 0 ? _managedSizing$width : Math.max(minWidth, Math.min(defaultWidth, maxWidth !== null && maxWidth !== void 0 ? maxWidth : Infinity));
131
+ // Before the first measurement, CSS caps the surface to its containing region.
132
+ // Resize preparation waits for the coordinator to resolve the same bounds for both inputs.
133
+ const getResizeBounds = useCallback(() => {
134
+ var _getManagedResizeBoun;
135
+ return (_getManagedResizeBoun = getManagedResizeBounds()) !== null && _getManagedResizeBoun !== void 0 ? _getManagedResizeBoun : {
136
+ min: '0px',
137
+ max: '0px'
138
+ };
139
+ }, [getManagedResizeBounds]);
140
+ const onCompleteResize = useCallback(width => {
141
+ // Keyboard analytics are debounced; retain the mode that produced this width,
142
+ // rather than the layout mode when the delayed callback eventually runs.
143
+ const mode = getMode();
144
+ completedResizeModeRef.current = mode;
145
+ onManagedResizeEnd(mode, width);
146
+ onWidthChange(width);
147
+ }, [getMode, onManagedResizeEnd, onWidthChange]);
148
+ const onResizeStartInternal = useCallback(() => {
149
+ dragMode.current = getMode();
150
+ onManagedResizeStart(dragMode.current);
151
+ }, [getMode, onManagedResizeStart]);
152
+ const onResizeInternal = useCallback(width => onManagedResize(dragMode.current, width), [onManagedResize]);
153
+ const handleResizeEnd = useCallback(event => onResizeEnd({
154
+ ...event,
155
+ mode: completedResizeModeRef.current
156
+ }), [onResizeEnd]);
157
+ return /*#__PURE__*/React.createElement("section", {
158
+ "aria-label": label,
159
+ "data-testid": testId,
160
+ "data-layout-with-panel-slot": "",
161
+ style: {
162
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-imported-style-values
163
+ [localPanelVar]: `${renderedWidth}px`
164
+ },
165
+ ref: mergedRef,
166
+ className: ax([isMotionUpliftEnabled && state === 'entering' && styles.entering, isMotionUpliftEnabled && state === 'exiting' && styles.exiting, styles.panelWrapper, styles.border, !managedSizing && styles.unmeasured, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'inline' && styles.managedInline, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'overlay' && styles.managedOverlay, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'inline' && styles.managedInlineBorder])
167
+ }, /*#__PURE__*/React.createElement(PanelSplitterProvider, {
168
+ panelId: localPanelSplitterId,
169
+ panelRef: ref,
170
+ resizingElementRef: layoutRef,
171
+ panelWidth: renderedWidth,
172
+ onCompleteResize: onCompleteResize,
173
+ prepareResize: prepareResize,
174
+ onResizeStartInternal: onResizeStartInternal,
175
+ onResizeInternal: onResizeInternal,
176
+ getResizeBounds: getResizeBounds,
177
+ isEnabled: managedSizing !== undefined || prepareResize !== undefined,
178
+ resizingCssVar: panelSplitterResizingVar,
179
+ position: "start"
180
+ }, /*#__PURE__*/React.createElement("div", {
181
+ ref: scrollRef,
182
+ "data-testid": testId && `${testId}--scroll-container`,
183
+ className: ax([styles.panelScrollContainer, state === 'entering' && styles.contentEntering, isMotionUpliftEnabled && state === 'entering' && contentEnteringDelayStyles.root, state === 'exiting' && styles.contentExiting])
184
+ }, isContentMotionEnabled && contentKey !== undefined ? /*#__PURE__*/React.createElement(ExitingPersistence, {
185
+ exitThenEnter: true
186
+ }, /*#__PURE__*/React.createElement(PanelContentMotion, {
187
+ key: contentKey
188
+ }, children)) : children, /*#__PURE__*/React.createElement(PanelSplitter, {
189
+ label: resizeLabel,
190
+ testId: testId && `${testId}--splitter`,
191
+ onResizeEnd: handleResizeEnd
192
+ }))));
193
+ }
@@ -5,7 +5,8 @@ import { ax, ix } from "@compiled/react/runtime";
5
5
  import { Fragment } from 'react';
6
6
  import { fg } from '@atlaskit/platform-feature-flags/fg';
7
7
  import { useSkipLink } from '../../../context/skip-links/use-skip-link';
8
- import { mainMinimumWidthVar } from '../constants';
8
+ import { defaultMainMinWidth, mainMinimumWidthVar } from '../constants';
9
+ import { MainMinWidthContext } from '../main-min-width-context';
9
10
  import { gridRootFitId, gridRootId } from '../root';
10
11
  import { useLayoutId } from '../use-layout-id';
11
12
  import { useLayoutMainSizing } from '../use-layout-main-sizing';
@@ -24,7 +25,7 @@ export function Main({
24
25
  skipLinkLabel = fg('platform_dst_nav4_skip_link_a11y_1') ? 'Main content' : 'Main Content',
25
26
  testId,
26
27
  id: providedId,
27
- minWidth = 320
28
+ minWidth = defaultMainMinWidth
28
29
  }) {
29
30
  const id = useLayoutId({
30
31
  providedId
@@ -40,5 +41,7 @@ export function Main({
40
41
  className: ax([mainElementStyles.root, xcss]),
41
42
  role: "main",
42
43
  "data-testid": testId
43
- }, children));
44
+ }, /*#__PURE__*/React.createElement(MainMinWidthContext.Provider, {
45
+ value: fg('platform-dst-chat-panel-layout') ? minWidth : defaultMainMinWidth
46
+ }, children)));
44
47
  }
@@ -0,0 +1,5 @@
1
+ import { createContext } from 'react';
2
+ import { defaultMainMinWidth } from './constants';
3
+
4
+ // Available during SSR, before Main registers with the measured sizing coordinator.
5
+ export const MainMinWidthContext = /*#__PURE__*/createContext(defaultMainMinWidth);
@@ -0,0 +1,27 @@
1
+ ._nd5l1gzg{grid-area:main}._129o1uuk{--p_lclPnlSsrPreferredW:var(--p_lclPnlCompactW)}
2
+ ._18m915vq{overflow-y:hidden}
3
+ ._18m91wug{overflow-y:auto}
4
+ ._19121cl4{isolation:isolate}
5
+ ._1bsb1osq{width:100%}
6
+ ._1e0c11p5{display:grid}
7
+ ._1e0c1txw{display:flex}
8
+ ._1f38177r{container-name:local-panel-layout}
9
+ ._1l7c1wug._1l7c1wug{grid-template-rows:auto}
10
+ ._1l7chkll._1l7chkll{grid-template-rows:1fr}
11
+ ._1lmc1m6a{grid-template-areas:"main"}
12
+ ._1lmcc53r{grid-template-areas:var(--p_lclPnlSsrAreas,"main")}
13
+ ._1reo15vq{overflow-x:hidden}
14
+ ._1reo1wug{overflow-x:auto}
15
+ ._1uwq15vq._1uwq15vq{overflow-x:hidden}
16
+ ._2z05hkll{grid-template-rows:1fr}
17
+ ._4t3i1osq{height:100%}
18
+ ._7cca15wb{container-type:inline-size}
19
+ ._82e815vq._82e815vq{overflow-y:hidden}
20
+ ._evra4a59{--p_lclPnlSsrInlineAreas:"main panel"}
21
+ ._f8371m6a._f8371m6a{grid-template-areas:"main"}
22
+ ._f8374a59._f8374a59{grid-template-areas:"main panel"}
23
+ ._vleg1orf._vleg1orf{grid-template-columns:minmax(var(--n_mainMinW,20pc),1fr) clamp(var(--p_lclPnlMinW),var(--p_lclPnlRsz,var(--p_lclPnlTrackW)),calc(100% - var(--n_mainMinW, 20pc)))}
24
+ ._vlegi47z._vlegi47z{grid-template-columns:minmax(0,1fr)}
25
+ ._yv0eggl6{grid-template-columns:var(--p_lclPnlSsrColumns,minmax(0,1fr))}
26
+ ._yv0ei47z{grid-template-columns:minmax(0,1fr)}
27
+ @media (min-width:64rem){._peezi77x{--p_lclPnlSsrPreferredW:var(--p_lclPnlTrackW)}}
@@ -0,0 +1,74 @@
1
+ /* panel-layout-shell.tsx generated by @compiled/babel-plugin v3.0.2 */
2
+ import "./panel-layout-shell.compiled.css";
3
+ import * as React from 'react';
4
+ import { ax, ix } from "@compiled/react/runtime";
5
+ import { useContext, useId } from 'react';
6
+ import { MainMinWidthContext } from './main-min-width-context';
7
+
8
+ // The grid uses runtime CSS variables and container sizing outside @atlaskit/css's bounded values.
9
+ const localPanelTrackWidthVar = '--p_lclPnlTrackW';
10
+ const localPanelResizingVar = '--p_lclPnlRsz';
11
+ const localPanelMinWidthVar = '--p_lclPnlMinW';
12
+ const styles = {
13
+ container: "_1f38177r _7cca15wb _4t3i1osq _1bsb1osq",
14
+ layout: "_1reo1wug _18m91wug _1e0c11p5 _7cca15wb _1lmc1m6a _yv0ei47z _2z05hkll _4t3i1osq _1bsb1osq",
15
+ mainContent: "_nd5l1gzg _1reo1wug _18m91wug _1e0c1txw _4t3i1osq _19121cl4",
16
+ panelUplift: "_1reo15vq _18m915vq",
17
+ unmeasured: "_evra4a59 _1lmcc53r _yv0eggl6 _129o1uuk _peezi77x",
18
+ managedInline: "_1uwq15vq _82e815vq _f8374a59 _1l7c1wug _vleg1orf",
19
+ managedOverlay: "_1uwq15vq _82e815vq _f8371m6a _vlegi47z _1l7chkll",
20
+ panelClosed: "_f8371m6a _vlegi47z _1l7chkll"
21
+ };
22
+ /**
23
+ * Grid and scrolling for Main and its local panel. Use inside navigation's Root and Main.
24
+ */
25
+ export function PanelLayoutShell({
26
+ children,
27
+ panel,
28
+ isPanelOpen,
29
+ panelWidth,
30
+ compactPanelWidth,
31
+ panelMinWidth = 320,
32
+ mode,
33
+ layoutRef,
34
+ shouldClipOverflow = false,
35
+ testId
36
+ }) {
37
+ const mainMinWidth = useContext(MainMinWidthContext);
38
+ const id = `local-panel-${useId().replace(/[^a-zA-Z0-9_-]/g, '_')}`;
39
+ // CSS query conditions cannot read custom properties. Only the numeric minimums and
40
+ // instance selector are generated here; this rule is removed after the first measurement,
41
+ // so live resizing never rewrites stylesheet text. The named container is local to this shell.
42
+ const inlineQuery = `@container local-panel-layout (min-width: ${mainMinWidth + panelMinWidth}px)`;
43
+ return /*#__PURE__*/React.createElement("div", {
44
+ className: ax([styles.container])
45
+ }, mode === undefined && isPanelOpen &&
46
+ /*#__PURE__*/
47
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-global-styles -- Instance-scoped SSR query supports caller-defined minimums; CSS variables are not allowed in query conditions.
48
+ React.createElement("style", null, `${inlineQuery} {
49
+ #${id} {
50
+ --p_lclPnlSsrAreas: var(--p_lclPnlSsrInlineAreas);
51
+ --p_lclPnlSsrColumns: minmax(0, 1fr) min(var(--p_lclPnlSsrPreferredW), calc(100% - ${mainMinWidth}px));
52
+ --p_lclPnlSsrArea: panel;
53
+ --p_lclPnlSsrWidth: 100%;
54
+ --p_lclPnlSsrBackground: ${"var(--ds-surface, #FFFFFF)"};
55
+ --p_lclPnlSsrShadow: none;
56
+ --p_lclPnlSsrBorder: ${"var(--ds-border-width, 1px)"} solid ${"var(--ds-border, #0B120E24)"};
57
+ }
58
+ }`), /*#__PURE__*/React.createElement("div", {
59
+ id: id,
60
+ "data-testid": testId,
61
+ ref: layoutRef,
62
+ style: {
63
+ // These runtime track widths are shared with the panel splitter.
64
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-unsafe-values
65
+ [localPanelTrackWidthVar]: `${panelWidth}px`,
66
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-unsafe-values
67
+ [localPanelMinWidthVar]: `${panelMinWidth}px`,
68
+ '--p_lclPnlCompactW': `${compactPanelWidth}px`
69
+ },
70
+ className: ax([styles.layout, mode === undefined && isPanelOpen && styles.unmeasured, shouldClipOverflow && styles.panelUplift, mode === 'inline' && styles.managedInline, mode === 'overlay' && styles.managedOverlay, !isPanelOpen && styles.panelClosed])
71
+ }, /*#__PURE__*/React.createElement("div", {
72
+ className: ax([styles.mainContent])
73
+ }, children), panel));
74
+ }
@@ -0,0 +1,147 @@
1
+ import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
2
+ import invariant from 'tiny-invariant';
3
+ import useStableRef from '@atlaskit/ds-lib/use-stable-ref';
4
+ import ExitingPersistence from '@atlaskit/motion/exiting-persistence';
5
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
6
+ import { getPanelLayoutDefaults } from './get-panel-layout-defaults';
7
+ import { LayoutAreaSizingContext } from './layout-area-sizing-context';
8
+ import { LayoutAreaSizingProvider } from './layout-area-sizing-provider';
9
+ import { LocalPanelSurface } from './local-panel-surface';
10
+ import { PanelLayoutShell } from './panel-layout-shell';
11
+ import { useLayoutAreaSizing } from './use-layout-area-sizing';
12
+
13
+ // Development-only ownership checks are scoped to each coordinator, not the whole page.
14
+ const mountedPanelLayouts = new WeakSet();
15
+ function CoordinatedPanelLayout({
16
+ children,
17
+ panel,
18
+ isPanelOpen,
19
+ panelKey,
20
+ panelWidth,
21
+ defaultPanelWidth,
22
+ compactDefaultPanelWidth,
23
+ panelMinWidth: suppliedMinWidth,
24
+ panelMaxWidth,
25
+ onPanelWidthChange,
26
+ onResizeEnd,
27
+ panelLabel = 'Panel',
28
+ resizeLabel = 'Resize panel',
29
+ testId,
30
+ layoutRef
31
+ }) {
32
+ var _ref, _ref2, _ref3, _state$width;
33
+ const [defaults] = useState(getPanelLayoutDefaults);
34
+ const panelMinWidth = suppliedMinWidth !== null && suppliedMinWidth !== void 0 ? suppliedMinWidth : defaults.panelMinWidth;
35
+ const coordinator = useContext(LayoutAreaSizingContext);
36
+ useEffect(() => {
37
+ if (process.env.NODE_ENV !== 'production' && coordinator) {
38
+ invariant(!mountedPanelLayouts.has(coordinator), 'Only one PanelLayout may be mounted per navigation Root/sizing coordinator, including closed hosts and LayoutWithPanel. Reuse the host to switch panel content.');
39
+ mountedPanelLayouts.add(coordinator);
40
+ return () => {
41
+ mountedPanelLayouts.delete(coordinator);
42
+ };
43
+ }
44
+ }, [coordinator]);
45
+ const [defaultWidth] = useState((_ref = defaultPanelWidth !== null && defaultPanelWidth !== void 0 ? defaultPanelWidth : panelWidth) !== null && _ref !== void 0 ? _ref : defaults.panelWidth);
46
+ const [compactWidth] = useState((_ref2 = (_ref3 = compactDefaultPanelWidth !== null && compactDefaultPanelWidth !== void 0 ? compactDefaultPanelWidth : defaultPanelWidth) !== null && _ref3 !== void 0 ? _ref3 : panelWidth) !== null && _ref2 !== void 0 ? _ref2 : defaults.compactPanelWidth);
47
+ // Use the controlled request before measurement too, without changing the mount-time default.
48
+ const fallbackWidth = Math.max(panelMinWidth, Math.min(panelWidth !== null && panelWidth !== void 0 ? panelWidth : defaultWidth, panelMaxWidth !== null && panelMaxWidth !== void 0 ? panelMaxWidth : Infinity));
49
+ const {
50
+ state,
51
+ prepareResize,
52
+ getResizeBounds,
53
+ startResize,
54
+ resize,
55
+ completeResize
56
+ } = useLayoutAreaSizing({
57
+ area: 'panel',
58
+ config: {
59
+ isOpen: isPanelOpen,
60
+ requestedWidth: panelWidth,
61
+ defaultWidth,
62
+ compactDefaultWidth: compactWidth,
63
+ minWidth: panelMinWidth,
64
+ maxWidth: panelMaxWidth
65
+ }
66
+ });
67
+ // Host callback identity is unrelated to sizing. Keep drag bindings stable while
68
+ // still delivering completions to the latest product-owned state and analytics.
69
+ const onWidthChangeRef = useStableRef(onPanelWidthChange);
70
+ const onResizeEndRef = useStableRef(onResizeEnd);
71
+ const handleWidthChange = useCallback(width => {
72
+ var _onWidthChangeRef$cur;
73
+ return (_onWidthChangeRef$cur = onWidthChangeRef.current) === null || _onWidthChangeRef$cur === void 0 ? void 0 : _onWidthChangeRef$cur.call(onWidthChangeRef, width);
74
+ }, [onWidthChangeRef]);
75
+ const handleResizeEnd = useCallback(event => {
76
+ var _onResizeEndRef$curre;
77
+ return (_onResizeEndRef$curre = onResizeEndRef.current) === null || _onResizeEndRef$curre === void 0 ? void 0 : _onResizeEndRef$curre.call(onResizeEndRef, event);
78
+ }, [onResizeEndRef]);
79
+ const isMotionEnabled = fg('platform-dst-motion-uplift-panel');
80
+ const surface = isPanelOpen && /*#__PURE__*/React.createElement(LocalPanelSurface, {
81
+ key: "local-panel",
82
+ contentKey: panelKey,
83
+ defaultWidth: fallbackWidth,
84
+ minWidth: panelMinWidth,
85
+ maxWidth: panelMaxWidth,
86
+ layoutRef: layoutRef,
87
+ managedSizing: state,
88
+ getManagedResizeBounds: getResizeBounds,
89
+ prepareResize: prepareResize,
90
+ onManagedResizeStart: startResize,
91
+ onManagedResize: resize,
92
+ onManagedResizeEnd: completeResize,
93
+ onWidthChange: handleWidthChange,
94
+ onResizeEnd: handleResizeEnd,
95
+ label: panelLabel,
96
+ resizeLabel: resizeLabel,
97
+ testId: testId && `${testId}--panel-slot`
98
+ }, panel);
99
+ return /*#__PURE__*/React.createElement(PanelLayoutShell, {
100
+ isPanelOpen: isPanelOpen,
101
+ panelWidth: (_state$width = state === null || state === void 0 ? void 0 : state.width) !== null && _state$width !== void 0 ? _state$width : fallbackWidth,
102
+ compactPanelWidth: Math.max(panelMinWidth, Math.min(panelWidth !== null && panelWidth !== void 0 ? panelWidth : compactWidth, panelMaxWidth !== null && panelMaxWidth !== void 0 ? panelMaxWidth : Infinity)),
103
+ panelMinWidth: panelMinWidth,
104
+ mode: state === null || state === void 0 ? void 0 : state.mode,
105
+ layoutRef: layoutRef,
106
+ shouldClipOverflow: isMotionEnabled,
107
+ testId: testId,
108
+ panel: isMotionEnabled ? /*#__PURE__*/React.createElement(ExitingPersistence, null, surface) : surface
109
+ }, children);
110
+ }
111
+
112
+ /**
113
+ * A coordinated local panel with caller-owned content and state. Keep this host mounted
114
+ * while closed to retain resized widths. Unmount it to reset its width preferences.
115
+ *
116
+ * Mount only one local-panel host per navigation Root/sizing coordinator, including
117
+ * closed hosts and panel-system's LayoutWithPanel. Switch content through the same host;
118
+ * separate Roots may each own a local panel.
119
+ *
120
+ * With chat layout enabled it shares Root's sizing coordinator. Otherwise it measures
121
+ * its own Main region, leaving existing navigation/chat behavior unchanged.
122
+ * Local-panel placement and resize constraints use this layout in both cases; disabling
123
+ * the chat-layout gate does not restore the previous panel-system layout engine.
124
+ */
125
+ export function PanelLayout(props) {
126
+ const parentSizing = useContext(LayoutAreaSizingContext);
127
+ const layoutRef = useRef(null);
128
+ const content = /*#__PURE__*/React.createElement(CoordinatedPanelLayout, {
129
+ layoutRef: layoutRef,
130
+ panel: props.panel,
131
+ isPanelOpen: props.isPanelOpen,
132
+ panelKey: props.panelKey,
133
+ panelWidth: props.panelWidth,
134
+ defaultPanelWidth: props.defaultPanelWidth,
135
+ compactDefaultPanelWidth: props.compactDefaultPanelWidth,
136
+ panelMinWidth: props.panelMinWidth,
137
+ panelMaxWidth: props.panelMaxWidth,
138
+ onPanelWidthChange: props.onPanelWidthChange,
139
+ onResizeEnd: props.onResizeEnd,
140
+ panelLabel: props.panelLabel,
141
+ resizeLabel: props.resizeLabel,
142
+ testId: props.testId
143
+ }, props.children);
144
+ return parentSizing ? content : /*#__PURE__*/React.createElement(LayoutAreaSizingProvider, {
145
+ layoutRef: layoutRef
146
+ }, content);
147
+ }
@@ -18,6 +18,7 @@ import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/util
18
18
  import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/utils/prevent-unhandled';
19
19
  import Tooltip from '@atlaskit/tooltip/Tooltip';
20
20
  import TooltipContainer from '@atlaskit/tooltip/TooltipContainer';
21
+ import { requestClosePopovers } from '@atlaskit/top-layer/page-surface/request-close-popovers';
21
22
  import VisuallyHidden from '@atlaskit/visually-hidden/visually-hidden';
22
23
  import { contentInsetBlockStart } from '../constants';
23
24
  import { convertResizeBoundToPixels } from './convert-resize-bound-to-pixels';
@@ -159,7 +160,9 @@ const PortaledPanelSplitter = ({
159
160
  min: 200,
160
161
  max: 500
161
162
  });
162
- const openLayerObserver = useOpenLayerObserver();
163
+ const openLayerObserver = useOpenLayerObserver({
164
+ isEnabled: !fg('platform-dst-top-layer')
165
+ });
163
166
 
164
167
  /**
165
168
  * This is a temporary workaround to enable the `SideNavPanelSplitter` component
@@ -173,7 +176,7 @@ const PortaledPanelSplitter = ({
173
176
  const initialClientXRef = useRef(null);
174
177
  // Live allocation changes panelWidth on every frame. Only the legacy path should
175
178
  // rebind the drag listeners when this value changes.
176
- const legacyPanelWidth = fg('platform-dst-chat-panel-layout') ? undefined : panelWidth;
179
+ const legacyPanelWidth = fg('platform-dst-chat-panel-layout') || onResizeInternal ? undefined : panelWidth;
177
180
  useEffect(() => {
178
181
  const splitter = splitterRef.current;
179
182
  invariant(splitter, 'Splitter ref must be set');
@@ -250,6 +253,8 @@ const PortaledPanelSplitter = ({
250
253
  onResizeStart === null || onResizeStart === void 0 ? void 0 : onResizeStart(resizeStartData);
251
254
 
252
255
  // Close any open layers when the user starts resizing
256
+ // Not gated. Top-layer tooltips (`platform-dst-top-layer-tooltip`) are not known to the observer.
257
+ requestClosePopovers();
253
258
  openLayerObserver === null || openLayerObserver === void 0 ? void 0 : openLayerObserver.closeLayers();
254
259
  },
255
260
  onDrag({
@@ -282,7 +287,7 @@ const PortaledPanelSplitter = ({
282
287
  position
283
288
  });
284
289
  const resizingWidth = `clamp(${resizeBounds.min}, ${targetWidth}px, ${resizeBounds.max})`;
285
- if (fg('platform-dst-chat-panel-layout') && onResizeInternal) {
290
+ if (onResizeInternal) {
286
291
  // The allocator needs below-minimum requests for the local-to-chat cascade.
287
292
  const currentBounds = getResizeBounds();
288
293
  const width = Math.max(0, Math.min(targetWidth, prepareResize ? window.innerWidth : convertResizeBoundToPixels(currentBounds.max)));
@@ -319,7 +324,7 @@ const PortaledPanelSplitter = ({
319
324
  const finalWidth = getPixelWidth(panel);
320
325
  // Persist the final preferred width before removing the imperative drag
321
326
  // variable, so the rendered width is continuous across pointer release.
322
- if (fg('platform-dst-chat-panel-layout')) {
327
+ if (fg('platform-dst-chat-panel-layout') || onResizeInternal) {
323
328
  flushSync(() => onCompleteResize(finalWidth));
324
329
  } else {
325
330
  onCompleteResize(finalWidth);
@@ -339,6 +344,8 @@ const PortaledPanelSplitter = ({
339
344
  onResizeStart: (...args) => {
340
345
  var _onResizeStartStableR;
341
346
  // Close any open layers when the user starts resizing with keyboard
347
+ // Not gated. Top-layer tooltips (`platform-dst-top-layer-tooltip`) are not known to the observer.
348
+ requestClosePopovers();
342
349
  openLayerObserver === null || openLayerObserver === void 0 ? void 0 : openLayerObserver.closeLayers();
343
350
  (_onResizeStartStableR = onResizeStartStableRef.current) === null || _onResizeStartStableR === void 0 ? void 0 : _onResizeStartStableR.call(onResizeStartStableRef, ...args);
344
351
  },
@@ -351,7 +358,7 @@ const PortaledPanelSplitter = ({
351
358
  const keyboardBoundsReady = useRef(false);
352
359
  const pendingKeyboardWidth = useRef(null);
353
360
  const handleSliderInputChange = useCallback(async (event, pendingKey) => {
354
- const isManagedResize = fg('platform-dst-chat-panel-layout') && onResizeInternal;
361
+ const isManagedResize = onResizeInternal;
355
362
  const sequence = focusSequence.current;
356
363
  let value = isManagedResize ? parseFloat(event.currentTarget.value) : parseInt(event.currentTarget.value);
357
364
  if (prepareResize) {
@@ -67,6 +67,8 @@ export function Root({
67
67
  const isChatPanelLayoutEnabled = fg('platform-dst-chat-panel-layout');
68
68
  const LayoutProvider = isChatPanelLayoutEnabled ? LayoutAreaSizingProvider : React.Fragment;
69
69
  const WidthContainer = isChatPanelLayoutEnabled ? LayoutFitContainers : React.Fragment;
70
+ // Top-layer surfaces are found through the DOM, so the observer is only needed when the gate is off.
71
+ const LayerObserverProvider = fg('platform-dst-top-layer') ? React.Fragment : OpenLayerObserver;
70
72
  useEffect(() => {
71
73
  if (process.env.NODE_ENV !== 'production') {
72
74
  const IGNORED_ELEMENTS = ['SCRIPT', 'STYLE'];
@@ -98,7 +100,7 @@ This message will not be displayed in production.
98
100
  isSideNavShortcutEnabled: isSideNavShortcutEnabled
99
101
  }, /*#__PURE__*/React.createElement(TopNavStartProvider, null, /*#__PURE__*/React.createElement(LayoutProvider, isChatPanelLayoutEnabled ? {
100
102
  layoutRef: ref
101
- } : {}, /*#__PURE__*/React.createElement(OpenLayerObserver, null, /*#__PURE__*/React.createElement(DangerouslyHoistSlotSizes.Provider, {
103
+ } : {}, /*#__PURE__*/React.createElement(LayerObserverProvider, null, /*#__PURE__*/React.createElement(DangerouslyHoistSlotSizes.Provider, {
102
104
  value: UNSAFE_dangerouslyHoistSlotSizes
103
105
  }, /*#__PURE__*/React.createElement(SkipLinksProvider, {
104
106
  label: skipLinksLabel,
@@ -0,0 +1,51 @@
1
+ import { bindAll } from 'bind-event-listener';
2
+ /**
3
+ * Calls `onChange` after any popover or dialog inside `within` opens or closes.
4
+ * Read the new state with `hasOpenDialogOrPopover`.
5
+ *
6
+ * `beforetoggle`, `toggle` and `close` do not bubble, so they are observed in the capture phase.
7
+ *
8
+ * `toggle` and `close` are queued tasks. When a surface closes because it unmounts,
9
+ * they fire after the element has left the DOM, so they never reach `within`.
10
+ * `beforetoggle` fires synchronously while the element is still inside `within`,
11
+ * so it also schedules `onChange` in a microtask, after the state has changed.
12
+ */
13
+ export function bindOpenLayerChange({
14
+ within,
15
+ onChange
16
+ }) {
17
+ let isBound = true;
18
+ function listener() {
19
+ onChange();
20
+ }
21
+ function deferredListener() {
22
+ queueMicrotask(() => {
23
+ if (isBound) {
24
+ onChange();
25
+ }
26
+ });
27
+ }
28
+ const unbind = bindAll(within, [{
29
+ type: 'beforetoggle',
30
+ listener: deferredListener,
31
+ options: {
32
+ capture: true
33
+ }
34
+ }, {
35
+ type: 'toggle',
36
+ listener,
37
+ options: {
38
+ capture: true
39
+ }
40
+ }, {
41
+ type: 'close',
42
+ listener,
43
+ options: {
44
+ capture: true
45
+ }
46
+ }]);
47
+ return function cleanup() {
48
+ isBound = false;
49
+ unbind();
50
+ };
51
+ }