@heymantle/litho 0.0.10 → 0.0.12

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 (91) hide show
  1. package/dist/cjs/components/Banner.js +6 -1
  2. package/dist/cjs/components/Button.js +33 -4
  3. package/dist/cjs/components/ButtonGroup.js +19 -4
  4. package/dist/cjs/components/Card.js +39 -3
  5. package/dist/cjs/components/ChoiceList.js +3 -2
  6. package/dist/cjs/components/Code.js +227 -0
  7. package/dist/cjs/components/Filters.js +1 -1
  8. package/dist/cjs/components/Frame.js +2 -2
  9. package/dist/cjs/components/Layout.js +16 -4
  10. package/dist/cjs/components/Link.js +35 -4
  11. package/dist/cjs/components/Modal.js +4 -0
  12. package/dist/cjs/components/Page.js +5 -2
  13. package/dist/cjs/components/Pane.js +669 -84
  14. package/dist/cjs/components/ResourceList.js +2 -2
  15. package/dist/cjs/components/TabNavigation.js +300 -0
  16. package/dist/cjs/components/TextField.js +3 -0
  17. package/dist/cjs/components/Tip.js +3 -0
  18. package/dist/cjs/components/Tooltip.js +12 -13
  19. package/dist/cjs/index.js +4 -0
  20. package/dist/cjs/stories/Pane.stories.js +352 -3
  21. package/dist/cjs/utilities/useBodyScrollLock.js +63 -0
  22. package/dist/cjs/utilities/useKeyboardAction.js +19 -0
  23. package/dist/cjs/utilities/useLocalStorage.js +126 -0
  24. package/dist/cjs/utilities/useMobile.js +92 -0
  25. package/dist/cjs/utilities/usePaneState.js +340 -0
  26. package/dist/cjs/utilities/useTabStorage.js +325 -0
  27. package/dist/esm/components/Banner.js +7 -2
  28. package/dist/esm/components/Button.js +33 -4
  29. package/dist/esm/components/ButtonGroup.js +19 -4
  30. package/dist/esm/components/Card.js +39 -3
  31. package/dist/esm/components/ChoiceList.js +3 -2
  32. package/dist/esm/components/Code.js +212 -0
  33. package/dist/esm/components/Filters.js +2 -2
  34. package/dist/esm/components/Frame.js +2 -2
  35. package/dist/esm/components/Layout.js +16 -4
  36. package/dist/esm/components/Link.js +31 -5
  37. package/dist/esm/components/Modal.js +4 -0
  38. package/dist/esm/components/Page.js +5 -2
  39. package/dist/esm/components/Pane.js +619 -83
  40. package/dist/esm/components/ResourceList.js +2 -2
  41. package/dist/esm/components/TabNavigation.js +285 -0
  42. package/dist/esm/components/TextField.js +4 -1
  43. package/dist/esm/components/Tip.js +4 -1
  44. package/dist/esm/components/Tooltip.js +12 -13
  45. package/dist/esm/index.js +1 -0
  46. package/dist/esm/stories/Pane.stories.js +346 -3
  47. package/dist/esm/utilities/useBodyScrollLock.js +53 -0
  48. package/dist/esm/utilities/useKeyboardAction.js +25 -0
  49. package/dist/esm/utilities/useLocalStorage.js +115 -0
  50. package/dist/esm/utilities/useMobile.js +79 -0
  51. package/dist/esm/utilities/usePaneState.js +334 -0
  52. package/dist/esm/utilities/useTabStorage.js +311 -0
  53. package/dist/types/components/Banner.d.ts.map +1 -1
  54. package/dist/types/components/Button.d.ts +2 -2
  55. package/dist/types/components/Button.d.ts.map +1 -1
  56. package/dist/types/components/ButtonGroup.d.ts.map +1 -1
  57. package/dist/types/components/Card.d.ts +34 -1
  58. package/dist/types/components/Card.d.ts.map +1 -1
  59. package/dist/types/components/Code.d.ts +28 -0
  60. package/dist/types/components/Code.d.ts.map +1 -0
  61. package/dist/types/components/Filters.d.ts.map +1 -1
  62. package/dist/types/components/Layout.d.ts +2 -0
  63. package/dist/types/components/Layout.d.ts.map +1 -1
  64. package/dist/types/components/Link.d.ts +4 -0
  65. package/dist/types/components/Link.d.ts.map +1 -1
  66. package/dist/types/components/Modal.d.ts +2 -0
  67. package/dist/types/components/Modal.d.ts.map +1 -1
  68. package/dist/types/components/Page.d.ts.map +1 -1
  69. package/dist/types/components/Pane.d.ts +2 -0
  70. package/dist/types/components/Pane.d.ts.map +1 -1
  71. package/dist/types/components/TabNavigation.d.ts +3 -0
  72. package/dist/types/components/TabNavigation.d.ts.map +1 -0
  73. package/dist/types/components/TextField.d.ts.map +1 -1
  74. package/dist/types/components/Tip.d.ts.map +1 -1
  75. package/dist/types/components/Tooltip.d.ts +2 -0
  76. package/dist/types/components/Tooltip.d.ts.map +1 -1
  77. package/dist/types/index.d.ts +1 -0
  78. package/dist/types/utilities/useBodyScrollLock.d.ts +12 -0
  79. package/dist/types/utilities/useBodyScrollLock.d.ts.map +1 -0
  80. package/dist/types/utilities/useKeyboardAction.d.ts +2 -0
  81. package/dist/types/utilities/useKeyboardAction.d.ts.map +1 -0
  82. package/dist/types/utilities/useLocalStorage.d.ts +13 -0
  83. package/dist/types/utilities/useLocalStorage.d.ts.map +1 -0
  84. package/dist/types/utilities/useMobile.d.ts +9 -0
  85. package/dist/types/utilities/useMobile.d.ts.map +1 -0
  86. package/dist/types/utilities/usePaneState.d.ts +2 -0
  87. package/dist/types/utilities/usePaneState.d.ts.map +1 -0
  88. package/dist/types/utilities/useTabStorage.d.ts +8 -0
  89. package/dist/types/utilities/useTabStorage.d.ts.map +1 -0
  90. package/index.css +77 -6
  91. package/package.json +1 -1
@@ -46,35 +46,504 @@ function _unsupported_iterable_to_array(o, minLen) {
46
46
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
47
47
  }
48
48
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
49
- import { CancelMajor, ArrowLeftMinor, MaximizeMajor, MinimizeMajor } from "@shopify/polaris-icons";
49
+ import { ArrowLeftMinor, ChevronLeftMinor, ChevronRightMinor } from "@shopify/polaris-icons";
50
50
  import { tv } from "tailwind-variants";
51
- import { createContext, useContext, useEffect, Children, useState } from "react";
51
+ import React, { createContext, useEffect, useLayoutEffect, useState, useMemo, useRef, useCallback } from "react";
52
52
  import Text from "./Text.js";
53
53
  import Button from "./Button.js";
54
54
  import Popover from "./Popover.js";
55
55
  import ActionList from "./ActionList.js";
56
+ import TabNavigation from "./TabNavigation.js";
56
57
  import { useFrame } from "./Frame.js";
58
+ import Icon from "./Icon.js";
59
+ import { usePaneState } from "../utilities/usePaneState.js";
60
+ import { useMobile } from "../utilities/useMobile.js";
61
+ import { useBodyScrollLock } from "../utilities/useBodyScrollLock.js";
62
+ import { useKeyboardAction } from "../utilities/useKeyboardAction.js";
63
+ import Tooltip from "./Tooltip.js";
64
+ // Reference counter for pane open state (shared across all Pane components)
65
+ var paneOpenCount = 0;
66
+ var MIN_PANE_WIDTH = 360;
67
+ var MAX_PANE_WIDTH = 800;
68
+ var COLLAPSED_WIDTH = 28;
69
+ var MOBILE_BREAKPOINT = 768;
70
+ var ZERO_WIDTH = '0px';
71
+ var COLLAPSED_WIDTH_STR = "".concat(COLLAPSED_WIDTH, "px");
72
+ var calculatePaneWidth = function(open, isMobile, isCollapsed, paneWidth) {
73
+ // Early returns for common cases (ordered by frequency/likelihood)
74
+ if (!open) return ZERO_WIDTH;
75
+ if (isCollapsed) return COLLAPSED_WIDTH_STR;
76
+ if (isMobile) return ZERO_WIDTH; // Full screen on mobile when expanded
77
+ // Only format paneWidth when actually needed
78
+ return "".concat(paneWidth, "px");
79
+ };
57
80
  var styles = tv({
58
- base: "Litho-Pane hidden md:block fixed inset-0 w-full md:left-auto md:max-w-[var(--litho-pane-width)] block bg-surface-lowest shadow-pane-inner pointer-events-auto"
81
+ base: "Litho-Pane fixed right-0 w-full md:w-[var(--litho-pane-width)] bg-surface-lowest shadow-pane-inner pointer-events-auto transition-all duration-200",
82
+ variants: {
83
+ collapsed: {
84
+ true: "md:w-7 md:h-screen md:flex-shrink-0"
85
+ },
86
+ isMobile: {
87
+ true: "top-0 bottom-0",
88
+ false: "top-0 h-full"
89
+ }
90
+ },
91
+ defaultVariants: {
92
+ isMobile: false
93
+ }
59
94
  });
60
95
  var innerStyles = tv({
61
- base: "relative flex flex-col overflow-y-auto h-full"
96
+ base: "relative flex flex-col h-full",
97
+ variants: {
98
+ collapsed: {
99
+ true: "bg-surface-lowest cursor-pointer overflow-visible",
100
+ false: "overflow-hidden"
101
+ }
102
+ }
62
103
  });
63
- var dragHandleStyles = tv({
64
- base: "cursor-col-resize absolute top-0 left-0 w-1 h-full transition-colors duration-300 hover-delay hover:bg-tint-3"
104
+ // Content styles will be applied inline with dynamic width
105
+ var getContentStyles = function(collapsed, width) {
106
+ return {
107
+ width: width,
108
+ transition: 'opacity 200ms ease-in-out',
109
+ pointerEvents: collapsed ? 'none' : 'auto',
110
+ opacity: collapsed ? 0 : 1
111
+ };
112
+ };
113
+ var resizeHandleStyles = tv({
114
+ base: "absolute top-0 left-0 w-1 h-full transition-colors duration-300 hover-delay hover:bg-tint-3 cursor-col-resize z-10",
115
+ variants: {
116
+ resizing: {
117
+ true: "bg-tint-3"
118
+ }
119
+ }
120
+ });
121
+ var collapseHandleStyles = tv({
122
+ base: "group/collapse-handle bg-surface-normal w-5 hover:w-6 h-10 flex-shrink-0 flex items-center justify-center cursor-pointer transition-all duration-200 ease-in-out rounded-r-sm",
123
+ variants: {
124
+ variant: {
125
+ expanded: "",
126
+ collapsed: "hover:bg-surface-lower"
127
+ }
128
+ },
129
+ defaultVariants: {
130
+ variant: "expanded"
131
+ }
65
132
  });
66
- var PaneContext = /*#__PURE__*/ createContext({});
133
+ export var PaneContext = /*#__PURE__*/ createContext({});
134
+ // Collapse button component to reduce duplication
135
+ function CollapseButton(param) {
136
+ var variant = param.variant, icon = param.icon, label = param.label, onClick = param.onClick, onKeyDown = param.onKeyDown, _param_stopPropagation = param.stopPropagation, stopPropagation = _param_stopPropagation === void 0 ? false : _param_stopPropagation;
137
+ return /*#__PURE__*/ _jsx("div", {
138
+ className: "absolute left-0 top-1/2 -translate-y-1/2",
139
+ children: /*#__PURE__*/ _jsx(Tooltip, {
140
+ content: label,
141
+ preferredPosition: "above",
142
+ alignment: "right",
143
+ children: /*#__PURE__*/ _jsx("div", {
144
+ className: collapseHandleStyles({
145
+ variant: variant
146
+ }),
147
+ onClick: function(e) {
148
+ if (stopPropagation) e.stopPropagation();
149
+ onClick(e);
150
+ },
151
+ role: "button",
152
+ tabIndex: 0,
153
+ onKeyDown: onKeyDown,
154
+ "aria-label": label,
155
+ children: /*#__PURE__*/ _jsx(Icon, {
156
+ source: icon,
157
+ color: "subdued"
158
+ })
159
+ })
160
+ })
161
+ });
162
+ }
67
163
  function Pane() {
68
164
  var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
69
- var open = props.open, onClose = props.onClose, children = props.children;
165
+ var _tabStorage_tabs;
166
+ var open = props.open, onClose = props.onClose, onOpen = props.onOpen, children = props.children, tabStorage = props.tabStorage, onTabRemove = props.onTabRemove, onExpandRef = props.onExpandRef, // Generic tab configuration
167
+ tabId = props.tabId, tabTitle = props.tabTitle, tabKey = props.tabKey, orgId = props.orgId;
70
168
  var setPaneIsOpen = useFrame().setPaneIsOpen;
71
- var classes = styles();
169
+ var paneRef = useRef(null);
170
+ var resizeHandleRef = useRef(null);
171
+ var paneWidthRef = useRef(null);
172
+ var resizeHandlersRef = useRef({
173
+ mouseMove: null,
174
+ mouseUp: null
175
+ });
176
+ var isMountedRef = useRef(true);
177
+ var hasRegisteredRef = useRef(false);
178
+ var previousTabCountRef = useRef(0);
179
+ var _useState = _sliced_to_array(useState(false), 2), isTransitioning = _useState[0], setIsTransitioning = _useState[1];
180
+ var isPastInitialMountRef = useRef(false);
181
+ var previousOpenPropRef = useRef(open);
182
+ // Get orgId from prop or tabStorage (tabStorage always has orgId set, either passed or 'default')
183
+ var currentOrgId = orgId || (tabStorage === null || tabStorage === void 0 ? void 0 : tabStorage.orgId);
184
+ // Use consolidated pane state hook with orgId
185
+ var _usePaneState = usePaneState(currentOrgId), paneState = _usePaneState.state, setPaneWidth = _usePaneState.setWidth, setIsCollapsed = _usePaneState.setCollapsed, setPaneOpen = _usePaneState.setOpen, refreshFromStorage = _usePaneState.refreshFromStorage;
186
+ var isCollapsed = paneState.collapsed;
187
+ var paneWidth = paneState.width;
188
+ // Helper to open the pane (updates localStorage and notifies parent)
189
+ var openPane = useCallback(function() {
190
+ setPaneOpen(true);
191
+ onOpen === null || onOpen === void 0 ? void 0 : onOpen();
192
+ }, [
193
+ setPaneOpen,
194
+ onOpen
195
+ ]);
196
+ // Helper to close the pane (updates localStorage and resets collapsed state)
197
+ var closePane = useCallback(function() {
198
+ setPaneOpen(false);
199
+ setIsCollapsed(false);
200
+ }, [
201
+ setPaneOpen,
202
+ setIsCollapsed
203
+ ]);
204
+ // Helper to close pane and notify parent (consolidates common pattern)
205
+ var closePaneAndNotify = useCallback(function() {
206
+ closePane();
207
+ onClose === null || onClose === void 0 ? void 0 : onClose();
208
+ }, [
209
+ closePane,
210
+ onClose
211
+ ]);
212
+ // Read from localStorage when pane opens (user action, not page load)
213
+ useEffect(function() {
214
+ if (open && refreshFromStorage && currentOrgId) {
215
+ refreshFromStorage();
216
+ }
217
+ }, [
218
+ open,
219
+ refreshFromStorage,
220
+ currentOrgId
221
+ ]);
222
+ // Update pane open state in localStorage when open prop changes
223
+ // The setPaneOpen setter will handle first-time state creation automatically
224
+ useEffect(function() {
225
+ if (!currentOrgId) return;
226
+ // Only update if open prop actually changed (not on every render)
227
+ if (previousOpenPropRef.current === open) return;
228
+ previousOpenPropRef.current = open;
229
+ if (open) {
230
+ // Set open to true when pane opens (creates state if it doesn't exist)
231
+ setPaneOpen(true);
232
+ } else {
233
+ // Set open to false when pane closes, and reset collapsed state
234
+ closePane();
235
+ }
236
+ }, [
237
+ open,
238
+ currentOrgId,
239
+ setPaneOpen,
240
+ closePane
241
+ ]);
242
+ var _useState1 = _sliced_to_array(useState(false), 2), isResizing = _useState1[0], setIsResizing = _useState1[1];
243
+ // Use mobile detection hook
244
+ var isMobile = useMobile(MOBILE_BREAKPOINT);
245
+ // Keep ref in sync with state
246
+ useEffect(function() {
247
+ paneWidthRef.current = paneWidth;
248
+ }, [
249
+ paneWidth
250
+ ]);
251
+ // Get the active tab data
252
+ var activeTab = useMemo(function() {
253
+ if (!(tabStorage === null || tabStorage === void 0 ? void 0 : tabStorage.activeTabId)) return null;
254
+ return tabStorage.tabs.find(function(tab) {
255
+ return tab.id === tabStorage.activeTabId;
256
+ }) || null;
257
+ }, [
258
+ tabStorage === null || tabStorage === void 0 ? void 0 : tabStorage.tabs,
259
+ tabStorage === null || tabStorage === void 0 ? void 0 : tabStorage.activeTabId
260
+ ]);
261
+ // Handle tab creation, activation, and ensure active tab exists when pane opens
262
+ useEffect(function() {
263
+ if (!open || !tabStorage) return;
264
+ // If tabId and tabTitle are provided, create/update/activate that tab
265
+ if (tabId && tabTitle) {
266
+ var existingTab = tabStorage.tabs.find(function(tab) {
267
+ return tab.id === tabId;
268
+ });
269
+ var wasAlreadyActive = existingTab && tabStorage.activeTabId === tabId;
270
+ if (existingTab) {
271
+ // Tab exists - switch to it if not already active
272
+ if (!wasAlreadyActive) {
273
+ tabStorage.setActiveTab(tabId);
274
+ }
275
+ // Update title/key if changed
276
+ if (existingTab.title !== tabTitle) {
277
+ tabStorage.updateTab(tabId, {
278
+ title: tabTitle
279
+ });
280
+ }
281
+ if (tabKey !== undefined && existingTab.key !== tabKey) {
282
+ tabStorage.updateTab(tabId, {
283
+ key: tabKey
284
+ });
285
+ }
286
+ } else {
287
+ // Create new tab
288
+ tabStorage.addTab({
289
+ id: tabId,
290
+ title: tabTitle,
291
+ key: tabKey
292
+ });
293
+ }
294
+ }
295
+ // Ensure an active tab is set (fallback if tabId/tabTitle not provided or if active tab doesn't exist)
296
+ if (tabStorage.tabs.length > 0) {
297
+ var activeTabExists = tabStorage.activeTabId && tabStorage.tabs.some(function(tab) {
298
+ return tab.id === tabStorage.activeTabId;
299
+ });
300
+ if (!activeTabExists) {
301
+ var firstTab = tabStorage.tabs[0];
302
+ if (firstTab) {
303
+ tabStorage.setActiveTab(firstTab.id);
304
+ }
305
+ }
306
+ }
307
+ }, [
308
+ open,
309
+ tabStorage,
310
+ tabId,
311
+ tabTitle,
312
+ tabKey
313
+ ]);
314
+ // Auto-open pane when tabs are added (user action, not page load)
315
+ useEffect(function() {
316
+ var _tabStorage_tabs;
317
+ if (!tabStorage || !currentOrgId) {
318
+ previousTabCountRef.current = 0;
319
+ return;
320
+ }
321
+ var currentTabCount = ((_tabStorage_tabs = tabStorage.tabs) === null || _tabStorage_tabs === void 0 ? void 0 : _tabStorage_tabs.length) || 0;
322
+ var previousTabCount = previousTabCountRef.current;
323
+ // Skip on initial mount - only react to tab count changes after mount
324
+ if (!isPastInitialMountRef.current) {
325
+ previousTabCountRef.current = currentTabCount;
326
+ isPastInitialMountRef.current = true;
327
+ return;
328
+ }
329
+ // If tab count increased (user added a tab) and pane is closed, open it
330
+ if (currentTabCount > previousTabCount && !open) {
331
+ openPane();
332
+ }
333
+ previousTabCountRef.current = currentTabCount;
334
+ }, [
335
+ tabStorage === null || tabStorage === void 0 ? void 0 : (_tabStorage_tabs = tabStorage.tabs) === null || _tabStorage_tabs === void 0 ? void 0 : _tabStorage_tabs.length,
336
+ currentOrgId,
337
+ open,
338
+ openPane
339
+ ]);
340
+ // Update frame context when pane opens/closes
341
+ // Uses reference counting to coordinate between multiple Pane components
342
+ useEffect(function() {
343
+ if (open && !hasRegisteredRef.current) {
344
+ paneOpenCount++;
345
+ if (paneOpenCount === 1) {
346
+ setPaneIsOpen(true);
347
+ }
348
+ hasRegisteredRef.current = true;
349
+ } else if (!open && hasRegisteredRef.current) {
350
+ paneOpenCount = Math.max(0, paneOpenCount - 1);
351
+ if (paneOpenCount === 0) {
352
+ setPaneIsOpen(false);
353
+ }
354
+ hasRegisteredRef.current = false;
355
+ }
356
+ return function() {
357
+ if (hasRegisteredRef.current) {
358
+ paneOpenCount = Math.max(0, paneOpenCount - 1);
359
+ if (paneOpenCount === 0) {
360
+ setPaneIsOpen(false);
361
+ }
362
+ hasRegisteredRef.current = false;
363
+ }
364
+ };
365
+ }, [
366
+ open,
367
+ setPaneIsOpen
368
+ ]);
369
+ // Uses shared utility with reference counting to coordinate with other components
370
+ useBodyScrollLock(open && isMobile && !isCollapsed);
371
+ // Memoize calculated width to avoid unnecessary recalculations
372
+ var calculatedWidth = useMemo(function() {
373
+ return calculatePaneWidth(open, isMobile, isCollapsed, paneWidth);
374
+ }, [
375
+ open,
376
+ isMobile,
377
+ isCollapsed,
378
+ paneWidth
379
+ ]);
380
+ // Calculate content width - keep width at paneWidth when collapsed so translateX works
381
+ var contentWidth = useMemo(function() {
382
+ if (!open) return '0px';
383
+ // On mobile, use full width when expanded; on desktop, use paneWidth
384
+ // Keep width at paneWidth even when collapsed so translateX(100%) can slide it off-screen
385
+ if (isMobile) return '100%';
386
+ return "".concat(paneWidth, "px");
387
+ }, [
388
+ open,
389
+ isMobile,
390
+ paneWidth
391
+ ]);
392
+ // Update CSS variable for pane width
393
+ useEffect(function() {
394
+ document.documentElement.style.setProperty('--litho-pane-width', calculatedWidth);
395
+ }, [
396
+ calculatedWidth
397
+ ]);
398
+ // Cleanup resize handlers on unmount
399
+ useEffect(function() {
400
+ isMountedRef.current = true;
401
+ return function() {
402
+ isMountedRef.current = false;
403
+ // Clean up any active resize handlers if component unmounts during drag
404
+ var _resizeHandlersRef_current = resizeHandlersRef.current, mouseMove = _resizeHandlersRef_current.mouseMove, mouseUp = _resizeHandlersRef_current.mouseUp;
405
+ if (mouseMove) {
406
+ document.removeEventListener('mousemove', mouseMove);
407
+ }
408
+ if (mouseUp) {
409
+ document.removeEventListener('mouseup', mouseUp);
410
+ }
411
+ resizeHandlersRef.current = {
412
+ mouseMove: null,
413
+ mouseUp: null
414
+ };
415
+ };
416
+ }, []);
417
+ // Handle resize drag with improved performance
418
+ // Updates CSS variable directly during drag for smooth performance
419
+ var handleResizeStart = useCallback(function(e) {
420
+ e.preventDefault();
421
+ if (!isMountedRef.current) return;
422
+ setIsResizing(true);
423
+ var startX = e.clientX;
424
+ // Use ref to get current width to avoid stale closure
425
+ var startWidth = paneWidthRef.current || paneWidth;
426
+ var currentWidth = startWidth;
427
+ // Disable transitions during drag for immediate response
428
+ var paneElement = paneRef.current;
429
+ var originalTransition = paneElement === null || paneElement === void 0 ? void 0 : paneElement.style.transition;
430
+ if (paneElement) {
431
+ paneElement.style.transition = 'none';
432
+ }
433
+ var handleMouseMove = function(e) {
434
+ // Check if component is still mounted before updating
435
+ if (!isMountedRef.current) return;
436
+ // Calculate new width directly without RAF for immediate response
437
+ var diff = startX - e.clientX; // Inverted because we're dragging from the left edge
438
+ var newWidth = Math.max(MIN_PANE_WIDTH, Math.min(MAX_PANE_WIDTH, startWidth + diff));
439
+ currentWidth = newWidth;
440
+ // Update CSS variable directly for immediate visual feedback (no re-render)
441
+ document.documentElement.style.setProperty('--litho-pane-width', "".concat(newWidth, "px"));
442
+ };
443
+ var handleMouseUp = function() {
444
+ // Check if component is still mounted before updating state
445
+ if (!isMountedRef.current) {
446
+ // Clean up listeners even if unmounted
447
+ document.removeEventListener('mousemove', handleMouseMove);
448
+ document.removeEventListener('mouseup', handleMouseUp);
449
+ resizeHandlersRef.current = {
450
+ mouseMove: null,
451
+ mouseUp: null
452
+ };
453
+ return;
454
+ }
455
+ // Restore transitions
456
+ if (paneElement) {
457
+ paneElement.style.transition = originalTransition || '';
458
+ }
459
+ // Sync final width to state (triggers re-render and localStorage save)
460
+ setPaneWidth(currentWidth);
461
+ setIsResizing(false);
462
+ document.removeEventListener('mousemove', handleMouseMove);
463
+ document.removeEventListener('mouseup', handleMouseUp);
464
+ resizeHandlersRef.current = {
465
+ mouseMove: null,
466
+ mouseUp: null
467
+ };
468
+ };
469
+ // Store handlers in ref for cleanup
470
+ resizeHandlersRef.current = {
471
+ mouseMove: handleMouseMove,
472
+ mouseUp: handleMouseUp
473
+ };
474
+ document.addEventListener('mousemove', handleMouseMove);
475
+ document.addEventListener('mouseup', handleMouseUp);
476
+ }, [
477
+ paneWidth,
478
+ setPaneWidth,
479
+ setIsResizing
480
+ ]);
481
+ var toggleCollapsed = useCallback(function() {
482
+ setIsCollapsed(function(prev) {
483
+ var newValue = !prev;
484
+ // Track transition start to prevent hover flash
485
+ setIsTransitioning(true);
486
+ // Clear transition flag after animation completes
487
+ setTimeout(function() {
488
+ setIsTransitioning(false);
489
+ }, 200); // Match transition duration
490
+ return newValue;
491
+ });
492
+ }, [
493
+ setIsCollapsed
494
+ ]);
495
+ // Expand pane function - exposed to parent via ref and tabStorage
496
+ var expandPane = useCallback(function() {
497
+ setIsCollapsed(false);
498
+ }, [
499
+ setIsCollapsed
500
+ ]);
501
+ // Expose expand function to parent via ref and tabStorage
502
+ useEffect(function() {
503
+ // Expose to parent ref if provided
504
+ if (onExpandRef) {
505
+ onExpandRef.current = expandPane;
506
+ }
507
+ // Expose to tabStorage for hooks to use
508
+ if (tabStorage) {
509
+ if (!tabStorage.expandPaneRef) {
510
+ tabStorage.expandPaneRef = {
511
+ current: null
512
+ };
513
+ }
514
+ tabStorage.expandPaneRef.current = expandPane;
515
+ }
516
+ // Cleanup: clear refs when component unmounts or refs change
517
+ return function() {
518
+ if (onExpandRef) {
519
+ onExpandRef.current = null;
520
+ }
521
+ if (tabStorage === null || tabStorage === void 0 ? void 0 : tabStorage.expandPaneRef) {
522
+ tabStorage.expandPaneRef.current = null;
523
+ }
524
+ };
525
+ }, [
526
+ onExpandRef,
527
+ tabStorage,
528
+ expandPane
529
+ ]);
530
+ // Expose isCollapsed synchronously using useLayoutEffect (runs before paint)
531
+ useLayoutEffect(function() {
532
+ if (tabStorage) {
533
+ tabStorage.isCollapsed = isCollapsed;
534
+ }
535
+ }, [
536
+ tabStorage,
537
+ isCollapsed
538
+ ]);
539
+ // Handle keyboard interactions
540
+ var handleKeyboardAction = useKeyboardAction();
541
+ // Handle Escape key to close
72
542
  useEffect(function() {
73
- setPaneIsOpen(open);
74
543
  if (!open || !onClose) return;
75
544
  var handleKeyDown = function(event) {
76
545
  if (event.key === "Escape") {
77
- onClose();
546
+ closePaneAndNotify();
78
547
  }
79
548
  };
80
549
  document.addEventListener("keydown", handleKeyDown);
@@ -83,36 +552,121 @@ function Pane() {
83
552
  };
84
553
  }, [
85
554
  open,
86
- onClose
555
+ onClose,
556
+ closePaneAndNotify
87
557
  ]);
558
+ var handleTabClick = useCallback(function(tabId) {
559
+ if (!tabStorage) return;
560
+ tabStorage.setActiveTab(tabId);
561
+ // Don't expand when clicking tabs - collapsed state should not be changed
562
+ }, [
563
+ tabStorage
564
+ ]);
565
+ var handleTabClose = useCallback(function(tabId) {
566
+ if (!tabStorage) return;
567
+ var tabToRemove = tabStorage.tabs.find(function(tab) {
568
+ return tab.id === tabId;
569
+ });
570
+ var remainingTabsCount = tabStorage.tabs.length - 1;
571
+ // Remove the tab first
572
+ tabStorage.removeTab(tabId);
573
+ // Only close the pane if this was the last tab
574
+ if (remainingTabsCount === 0 && open && onClose) {
575
+ closePaneAndNotify();
576
+ }
577
+ // Notify parent about the removed tab
578
+ if (onTabRemove) {
579
+ onTabRemove(tabId, tabToRemove);
580
+ }
581
+ }, [
582
+ tabStorage,
583
+ open,
584
+ onClose,
585
+ onTabRemove,
586
+ closePane
587
+ ]);
588
+ // Memoize style object to avoid recreating on every render
589
+ // Must be called before early return to maintain hook order
590
+ var paneStyle = useMemo(function() {
591
+ return isCollapsed ? {
592
+ width: "".concat(COLLAPSED_WIDTH, "px")
593
+ } : undefined;
594
+ }, [
595
+ isCollapsed
596
+ ]);
597
+ var classes = styles({
598
+ collapsed: isCollapsed,
599
+ isMobile: isMobile
600
+ });
601
+ var innerClasses = innerStyles({
602
+ collapsed: isCollapsed
603
+ });
88
604
  if (!open) {
89
605
  return null;
90
606
  }
91
607
  return /*#__PURE__*/ _jsx(PaneContext.Provider, {
92
608
  value: {
93
- onClose: onClose
609
+ activeTab: activeTab,
610
+ onClose: onClose,
611
+ isCollapsed: isCollapsed,
612
+ toggleCollapsed: toggleCollapsed
94
613
  },
95
614
  children: /*#__PURE__*/ _jsx("div", {
615
+ ref: paneRef,
96
616
  className: classes,
97
- children: /*#__PURE__*/ _jsx("div", {
98
- className: innerStyles(),
99
- children: children
617
+ style: paneStyle,
618
+ children: /*#__PURE__*/ _jsxs("div", {
619
+ className: "".concat(innerClasses, " ").concat(isCollapsed && !isTransitioning ? 'hover:bg-[#D5D3D1]' : ''),
620
+ onClick: isCollapsed ? toggleCollapsed : undefined,
621
+ children: [
622
+ /*#__PURE__*/ _jsxs("div", {
623
+ className: "flex flex-col overflow-y-auto overflow-x-hidden h-full",
624
+ style: getContentStyles(isCollapsed, contentWidth),
625
+ children: [
626
+ tabStorage && tabStorage.tabs.length > 0 && /*#__PURE__*/ _jsx(TabNavigation, {
627
+ tabs: tabStorage.tabs,
628
+ activeTabId: tabStorage.activeTabId,
629
+ onTabClick: handleTabClick,
630
+ onTabClose: handleTabClose
631
+ }),
632
+ children,
633
+ /*#__PURE__*/ _jsx("div", {
634
+ ref: resizeHandleRef,
635
+ className: resizeHandleStyles({
636
+ resizing: isResizing
637
+ }),
638
+ onMouseDown: handleResizeStart
639
+ }),
640
+ /*#__PURE__*/ _jsx(CollapseButton, {
641
+ variant: "expanded",
642
+ icon: ChevronRightMinor,
643
+ label: "Collapse pane",
644
+ onClick: toggleCollapsed,
645
+ onKeyDown: function(e) {
646
+ return handleKeyboardAction(e, toggleCollapsed);
647
+ }
648
+ })
649
+ ]
650
+ }),
651
+ isCollapsed && /*#__PURE__*/ _jsx(CollapseButton, {
652
+ variant: "collapsed",
653
+ icon: ChevronLeftMinor,
654
+ label: "Expand pane",
655
+ onClick: toggleCollapsed,
656
+ onKeyDown: function(e) {
657
+ return handleKeyboardAction(e, toggleCollapsed);
658
+ },
659
+ stopPropagation: true
660
+ })
661
+ ]
100
662
  })
101
663
  })
102
664
  });
103
665
  }
104
666
  function Header() {
105
667
  var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
106
- var title = props.title, subtitle = props.subtitle, _props_actions = props.actions, actions = _props_actions === void 0 ? [] : _props_actions, _props_showCloseButton = props.showCloseButton, showCloseButton = _props_showCloseButton === void 0 ? true : _props_showCloseButton, _props_bottomBorder = props.bottomBorder, bottomBorder = _props_bottomBorder === void 0 ? true : _props_bottomBorder, subHeader = props.subHeader, tmp = props.backAction, _backAction = tmp === void 0 ? null : tmp, _props_expanded = props.expanded, expanded = _props_expanded === void 0 ? false : _props_expanded, _props_onToggleExpand = props.onToggleExpand, onToggleExpand = _props_onToggleExpand === void 0 ? function() {} : _props_onToggleExpand, _props_allowExpand = props.allowExpand, allowExpand = _props_allowExpand === void 0 ? true : _props_allowExpand;
107
- var onClose = useContext(PaneContext).onClose;
108
- var showActionsOrClose = actions.length > 0 || showCloseButton;
668
+ var title = props.title, subtitle = props.subtitle, _props_actions = props.actions, actions = _props_actions === void 0 ? [] : _props_actions, _props_bottomBorder = props.bottomBorder, bottomBorder = _props_bottomBorder === void 0 ? true : _props_bottomBorder, subHeader = props.subHeader, tmp = props.backAction, _backAction = tmp === void 0 ? null : tmp;
109
669
  var _useState = _sliced_to_array(useState(null), 2), showActionsPopover = _useState[0], setShowActionsPopover = _useState[1];
110
- useEffect(function() {
111
- var width = expanded ? 'var(--litho-pane-width-expanded)' : 'var(--litho-pane-width-default)';
112
- document.documentElement.style.setProperty('--litho-pane-width', width);
113
- }, [
114
- expanded
115
- ]);
116
670
  var backAction = _backAction ? /*#__PURE__*/ _jsx("div", {
117
671
  className: "Litho-Page__Header--BackAction",
118
672
  children: /*#__PURE__*/ _jsx(Button, {
@@ -128,10 +682,10 @@ function Header() {
128
682
  className: "Litho-Pane__Header",
129
683
  children: [
130
684
  /*#__PURE__*/ _jsxs("div", {
131
- className: "flex items-center justify-between gap-2 min-h-14 px-2 border-edge-subdued ".concat(bottomBorder ? "border-b" : ""),
685
+ className: "flex items-center justify-between px-2 border-edge-subdued min-h-14 ".concat(bottomBorder ? "border-b" : ""),
132
686
  children: [
133
687
  /*#__PURE__*/ _jsxs("div", {
134
- className: "flex items-center gap-2 flex-1",
688
+ className: "flex items-center gap-2",
135
689
  children: [
136
690
  backAction,
137
691
  /*#__PURE__*/ _jsxs("div", {
@@ -153,62 +707,43 @@ function Header() {
153
707
  })
154
708
  ]
155
709
  }),
156
- showActionsOrClose && /*#__PURE__*/ _jsxs("div", {
710
+ /*#__PURE__*/ _jsx("div", {
157
711
  className: "flex items-center justify-end",
158
- children: [
159
- actions.map(function(action, index) {
160
- var showPopover = action.subActions || action.subSections;
161
- var innerContent = /*#__PURE__*/ _jsx(Button, {
162
- plain: !action.destructive,
163
- onClick: showPopover ? function() {
164
- return setShowActionsPopover(index);
165
- } : action.onAction || action.onClick,
166
- disabled: action.disabled,
167
- icon: action.icon,
168
- loading: action.loading,
169
- external: action.external,
170
- url: action.url,
171
- destructive: action.destructive,
172
- tooltip: action.tooltip && showActionsPopover !== index ? action.tooltip : undefined,
173
- tooltipPosition: "below"
174
- }, index);
175
- if (showPopover) {
176
- return /*#__PURE__*/ _jsx(Popover, {
177
- active: showActionsPopover === index,
178
- onClose: function() {
179
- return setShowActionsPopover(null);
180
- },
181
- activator: innerContent,
182
- preferredAlignment: "left",
183
- children: /*#__PURE__*/ _jsx(ActionList, {
184
- sections: action.subSections,
185
- items: action.subActions,
186
- onActionAnyItem: function() {
187
- return setShowActionsPopover(null);
188
- }
189
- })
190
- });
191
- }
192
- return innerContent;
193
- }),
194
- allowExpand && /*#__PURE__*/ _jsx("div", {
195
- className: "hidden xl:block",
196
- children: /*#__PURE__*/ _jsx(Button, {
197
- plain: true,
198
- icon: expanded ? MinimizeMajor : MaximizeMajor,
199
- onClick: onToggleExpand,
200
- tooltip: expanded ? "Minimize" : "Expand",
201
- tooltipPosition: "below"
202
- })
203
- }),
204
- showCloseButton && /*#__PURE__*/ _jsx(Button, {
205
- plain: true,
206
- icon: CancelMajor,
207
- onClick: onClose,
208
- tooltip: "Close",
712
+ children: actions.length > 0 && actions.map(function(action, index) {
713
+ var showPopover = action.subActions || action.subSections;
714
+ var innerContent = /*#__PURE__*/ _jsx(Button, {
715
+ plain: !action.destructive,
716
+ onClick: showPopover ? function() {
717
+ return setShowActionsPopover(index);
718
+ } : action.onAction || action.onClick,
719
+ disabled: action.disabled,
720
+ icon: action.icon,
721
+ loading: action.loading,
722
+ external: action.external,
723
+ url: action.url,
724
+ destructive: action.destructive,
725
+ tooltip: action.tooltip && showActionsPopover !== index ? action.tooltip : undefined,
209
726
  tooltipPosition: "below"
210
- })
211
- ]
727
+ }, index);
728
+ if (showPopover) {
729
+ return /*#__PURE__*/ _jsx(Popover, {
730
+ active: showActionsPopover === index,
731
+ onClose: function() {
732
+ return setShowActionsPopover(null);
733
+ },
734
+ activator: innerContent,
735
+ preferredAlignment: "left",
736
+ children: /*#__PURE__*/ _jsx(ActionList, {
737
+ sections: action.subSections,
738
+ items: action.subActions,
739
+ onActionAnyItem: function() {
740
+ return setShowActionsPopover(null);
741
+ }
742
+ })
743
+ });
744
+ }
745
+ return innerContent;
746
+ })
212
747
  })
213
748
  ]
214
749
  }),
@@ -224,7 +759,7 @@ function Content() {
224
759
  var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
225
760
  var children = props.children, _props_padded = props.padded, padded = _props_padded === void 0 ? true : _props_padded, className = props.className;
226
761
  return /*#__PURE__*/ _jsx("div", {
227
- className: "Litho-Pane__Content flex-1 overflow-auto ".concat(padded ? "p-2" : "", " ").concat(className),
762
+ className: "Litho-Pane__Content flex-1 overflow-auto ".concat(padded ? "p-2" : "", " ").concat(className || ""),
228
763
  children: children
229
764
  });
230
765
  }
@@ -232,8 +767,9 @@ Pane.Content = Content;
232
767
  function Footer() {
233
768
  var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
234
769
  var children = props.children, _props_topBorder = props.topBorder, topBorder = _props_topBorder === void 0 ? true : _props_topBorder;
770
+ var isMobile = useMobile(MOBILE_BREAKPOINT);
235
771
  return /*#__PURE__*/ _jsx("div", {
236
- className: "Litho-Pane__Footer min-h-12 ".concat(topBorder ? "border-t border-edge-subdued" : ""),
772
+ className: "Litho-Pane__Footer min-h-12 ".concat(topBorder ? "border-t border-edge-subdued" : "", " ").concat(isMobile ? "sticky bottom-0 bg-surface-lowest z-10" : ""),
237
773
  children: children
238
774
  });
239
775
  }