@harborclient/sdk 1.0.67 → 1.0.68

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 (45) hide show
  1. package/dist/components/FooterIcon/index.d.ts +2 -1
  2. package/dist/components/FooterIcon/index.d.ts.map +1 -1
  3. package/dist/components/FooterIcon/index.js +3 -2
  4. package/dist/components/TabBar/ClosingTabShell.d.ts +20 -0
  5. package/dist/components/TabBar/ClosingTabShell.d.ts.map +1 -0
  6. package/dist/components/TabBar/ClosingTabShell.js +126 -0
  7. package/dist/components/TabBar/TabBarShell.d.ts +58 -0
  8. package/dist/components/TabBar/TabBarShell.d.ts.map +1 -0
  9. package/dist/components/TabBar/TabBarShell.js +37 -0
  10. package/dist/components/TabBar/TabContextMenu.d.ts +32 -0
  11. package/dist/components/TabBar/TabContextMenu.d.ts.map +1 -0
  12. package/dist/components/TabBar/TabContextMenu.js +133 -0
  13. package/dist/components/TabBar/TabNewButton.d.ts +26 -0
  14. package/dist/components/TabBar/TabNewButton.d.ts.map +1 -0
  15. package/dist/components/TabBar/TabNewButton.js +14 -0
  16. package/dist/components/TabBar/index.d.ts +92 -0
  17. package/dist/components/TabBar/index.d.ts.map +1 -0
  18. package/dist/components/TabBar/index.js +224 -0
  19. package/dist/components/TabBar/tabCloseMenuHelpers.d.ts +41 -0
  20. package/dist/components/TabBar/tabCloseMenuHelpers.d.ts.map +1 -0
  21. package/dist/components/TabBar/tabCloseMenuHelpers.js +48 -0
  22. package/dist/components/TabBar/types.d.ts +60 -0
  23. package/dist/components/TabBar/types.d.ts.map +1 -0
  24. package/dist/components/TabBar/types.js +1 -0
  25. package/dist/components/TabBar/useExitingTabItems.d.ts +73 -0
  26. package/dist/components/TabBar/useExitingTabItems.d.ts.map +1 -0
  27. package/dist/components/TabBar/useExitingTabItems.js +103 -0
  28. package/dist/components/TabBar/useSortableTabItem.d.ts +31 -0
  29. package/dist/components/TabBar/useSortableTabItem.d.ts.map +1 -0
  30. package/dist/components/TabBar/useSortableTabItem.js +29 -0
  31. package/dist/components/Toolbar/index.d.ts.map +1 -1
  32. package/dist/components/Toolbar/index.js +21 -5
  33. package/dist/components/classes.d.ts +9 -0
  34. package/dist/components/classes.d.ts.map +1 -1
  35. package/dist/components/classes.js +15 -2
  36. package/dist/components/footerBarUtils.d.ts +11 -0
  37. package/dist/components/footerBarUtils.d.ts.map +1 -0
  38. package/dist/components/footerBarUtils.js +10 -0
  39. package/dist/components/index.d.ts +3 -1
  40. package/dist/components/index.d.ts.map +1 -1
  41. package/dist/components/index.js +3 -1
  42. package/dist/styles.css +12 -0
  43. package/dist/types.d.ts +2 -1
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +7 -1
@@ -24,7 +24,8 @@ interface Props extends Omit<ComponentPropsWithoutRef<'button'>, 'aria-label' |
24
24
  title?: string;
25
25
  /**
26
26
  * Active-state background treatment. Use `"selection"` to match sidebar
27
- * toolbar icon toggles; defaults to `"surface"` for the action menu icon.
27
+ * toolbar icon toggles (`bg-sidebar-section`); defaults to `"surface"` for
28
+ * the action menu icon.
28
29
  */
29
30
  activeStyle?: ActiveStyle;
30
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FooterIcon/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAI3D;;GAEG;AACH,KAAK,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAE3C,UAAU,KAAM,SAAQ,IAAI,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,cAAc,CAAC;IAC7F;;OAEG;IACH,IAAI,EAAE,cAAc,CAAC;IAErB;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAwBD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,MAAM,EACN,OAAO,EACP,KAAK,EACL,KAAK,EACL,WAAuB,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAqBrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FooterIcon/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAK3D;;GAEG;AACH,KAAK,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAE3C,UAAU,KAAM,SAAQ,IAAI,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,cAAc,CAAC;IAC7F;;OAEG;IACH,IAAI,EAAE,cAAc,CAAC;IAErB;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAuBD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,MAAM,EACN,OAAO,EACP,KAAK,EACL,KAAK,EACL,WAAuB,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAqBrB"}
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
2
2
  import { FaIcon } from '../FaIcon/index.js';
3
+ import { footerIconButtonSizeClass } from '../footerBarUtils.js';
3
4
  import { cn } from '../utils.js';
4
- const footerIconBase = 'inline-flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded-md border-none app-no-drag';
5
+ const footerIconBase = `inline-flex ${footerIconButtonSizeClass} shrink-0 cursor-pointer items-center justify-center rounded-md border-none app-no-drag`;
5
6
  /**
6
7
  * Square icon toggle styles for footer sidebar buttons.
7
8
  *
@@ -12,7 +13,7 @@ const footerIconBase = 'inline-flex h-7 w-7 shrink-0 cursor-pointer items-center
12
13
  function footerIconButton(active, activeStyle) {
13
14
  if (activeStyle === 'selection') {
14
15
  return active
15
- ? `${footerIconBase} bg-selection text-text`
16
+ ? `${footerIconBase} bg-sidebar-section text-text`
16
17
  : `${footerIconBase} bg-transparent text-text hover:bg-selection focus-visible:bg-selection focus-visible:text-text`;
17
18
  }
18
19
  return active
@@ -0,0 +1,20 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ interface Props {
3
+ /**
4
+ * Tab row content to measure and collapse horizontally.
5
+ */
6
+ children: ReactNode;
7
+ /**
8
+ * Called after the close width transition finishes.
9
+ */
10
+ onComplete: () => void;
11
+ }
12
+ /**
13
+ * Horizontally collapses a tab row with a quick width and opacity "zap out".
14
+ *
15
+ * Keeps children mounted until the width transition reaches zero so siblings
16
+ * do not reflow until the animation completes.
17
+ */
18
+ export declare function ClosingTabShell({ children, onComplete }: Props): JSX.Element | null;
19
+ export {};
20
+ //# sourceMappingURL=ClosingTabShell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClosingTabShell.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/ClosingTabShell.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAsD,MAAM,OAAO,CAAC;AAEhG,UAAU,KAAK;IACb;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AA+HD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CA0CnF"}
@@ -0,0 +1,126 @@
1
+ import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from '@harborclient/sdk/react';
3
+ /**
4
+ * Returns whether the user prefers reduced motion.
5
+ *
6
+ * @returns True when the OS requests minimized animation.
7
+ */
8
+ function prefersReducedMotion() {
9
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
10
+ }
11
+ /**
12
+ * Manages width collapse state for a single closing tab shell.
13
+ *
14
+ * @param onComplete - Invoked after the close animation finishes.
15
+ * @returns State and handlers for the animated shell.
16
+ */
17
+ function useClosingTabShell(onComplete) {
18
+ const innerRef = useRef(null);
19
+ const completedRef = useRef(false);
20
+ const [collapsed, setCollapsed] = useState(false);
21
+ const [contentWidth, setContentWidth] = useState(0);
22
+ const [transitionEnabled, setTransitionEnabled] = useState(false);
23
+ const [reducedMotion, setReducedMotion] = useState(prefersReducedMotion);
24
+ /**
25
+ * Subscribes to OS reduced-motion preference changes.
26
+ */
27
+ useEffect(() => {
28
+ const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
29
+ /**
30
+ * Updates reduced-motion state when the OS preference changes.
31
+ */
32
+ const handleChange = () => {
33
+ setReducedMotion(mediaQuery.matches);
34
+ };
35
+ mediaQuery.addEventListener('change', handleChange);
36
+ return () => mediaQuery.removeEventListener('change', handleChange);
37
+ }, []);
38
+ /**
39
+ * Measures the tab row so the outer shell can animate from its natural width to zero.
40
+ */
41
+ useLayoutEffect(() => {
42
+ if (!innerRef.current) {
43
+ return;
44
+ }
45
+ const element = innerRef.current;
46
+ /**
47
+ * Writes the latest measured width from layout or resize observation.
48
+ *
49
+ * @param width - Measured inner width in pixels.
50
+ */
51
+ const applyWidth = (width) => {
52
+ if (width > 0) {
53
+ setContentWidth(width);
54
+ }
55
+ };
56
+ const observer = new ResizeObserver(([entry]) => {
57
+ applyWidth(entry.contentRect.width);
58
+ });
59
+ observer.observe(element);
60
+ applyWidth(element.getBoundingClientRect().width);
61
+ return () => observer.disconnect();
62
+ }, []);
63
+ /**
64
+ * Starts the collapse animation after the first paint, or completes immediately
65
+ * when reduced motion is enabled.
66
+ */
67
+ useEffect(() => {
68
+ if (reducedMotion) {
69
+ onComplete();
70
+ return;
71
+ }
72
+ if (contentWidth <= 0) {
73
+ return;
74
+ }
75
+ requestAnimationFrame(() => {
76
+ setTransitionEnabled(true);
77
+ requestAnimationFrame(() => {
78
+ setCollapsed(true);
79
+ });
80
+ });
81
+ }, [contentWidth, onComplete, reducedMotion]);
82
+ /**
83
+ * Finishes the exit animation once the width transition completes.
84
+ */
85
+ const handleTransitionEnd = useCallback((event) => {
86
+ if (event.propertyName !== 'width' || completedRef.current) {
87
+ return;
88
+ }
89
+ completedRef.current = true;
90
+ onComplete();
91
+ }, [onComplete]);
92
+ return {
93
+ collapsed,
94
+ contentWidth,
95
+ innerRef,
96
+ reducedMotion,
97
+ transitionEnabled,
98
+ handleTransitionEnd
99
+ };
100
+ }
101
+ /**
102
+ * Horizontally collapses a tab row with a quick width and opacity "zap out".
103
+ *
104
+ * Keeps children mounted until the width transition reaches zero so siblings
105
+ * do not reflow until the animation completes.
106
+ */
107
+ export function ClosingTabShell({ children, onComplete }) {
108
+ const { collapsed, contentWidth, innerRef, reducedMotion, transitionEnabled, handleTransitionEnd } = useClosingTabShell(onComplete);
109
+ if (reducedMotion) {
110
+ return null;
111
+ }
112
+ const outerClassName = [
113
+ 'flex shrink-0 overflow-hidden',
114
+ transitionEnabled
115
+ ? 'transition-[width] duration-150 ease-in motion-reduce:transition-none'
116
+ : 'transition-none'
117
+ ].join(' ');
118
+ const innerClassName = [
119
+ 'flex shrink-0',
120
+ transitionEnabled
121
+ ? 'transition-[opacity,transform] duration-150 ease-in origin-right motion-reduce:transition-none'
122
+ : 'transition-none',
123
+ collapsed ? 'scale-x-95 opacity-0' : 'scale-x-100 opacity-100'
124
+ ].join(' ');
125
+ return (_jsx("div", { className: outerClassName, style: { width: collapsed ? 0 : contentWidth }, onTransitionEnd: handleTransitionEnd, "aria-hidden": true, inert: true, children: _jsx("div", { ref: innerRef, className: innerClassName, children: children }) }));
126
+ }
@@ -0,0 +1,58 @@
1
+ import type { JSX, MouseEvent } from 'react';
2
+ import type { TabBarItem, TabBarSortableCursor } from './types.js';
3
+ interface Props<TId extends string | number> {
4
+ /**
5
+ * Tab row data to render.
6
+ */
7
+ item: TabBarItem<TId>;
8
+ /**
9
+ * Tab order index for the tab label; all tabs stay in sequential Tab order.
10
+ */
11
+ tabIndex: number;
12
+ /**
13
+ * Stable dnd-kit sortable id for this tab row.
14
+ */
15
+ sortableId: string;
16
+ /**
17
+ * When true, drag reordering is disabled for this tab.
18
+ */
19
+ sortableDisabled?: boolean;
20
+ /**
21
+ * When true, renders a non-interactive snapshot for the close animation.
22
+ */
23
+ exiting?: boolean;
24
+ /**
25
+ * Prefix for tab element ids (for example `request-tab-`).
26
+ */
27
+ tabIdPrefix: string;
28
+ /**
29
+ * Prefix for linked tab panel ids (for example `request-tabpanel-`).
30
+ */
31
+ panelIdPrefix: string;
32
+ /**
33
+ * Maximum width class for the tab shell.
34
+ */
35
+ maxTabWidthClass?: string;
36
+ /**
37
+ * Cursor style when sortable drag is enabled.
38
+ */
39
+ sortableCursor?: TabBarSortableCursor;
40
+ /**
41
+ * Called when the user selects this tab.
42
+ */
43
+ onSelect: (id: TId) => void;
44
+ /**
45
+ * Called when the user closes this tab.
46
+ */
47
+ onClose: (id: TId) => void;
48
+ /**
49
+ * Called when the user opens the tab context menu.
50
+ */
51
+ onContextMenu?: (id: TId, event: MouseEvent<HTMLDivElement>) => void;
52
+ }
53
+ /**
54
+ * Single document-style tab row with close button and optional drag reordering.
55
+ */
56
+ export declare function TabBarShell<TId extends string | number>({ item, tabIndex, sortableId, sortableDisabled, exiting, tabIdPrefix, panelIdPrefix, maxTabWidthClass, sortableCursor, onSelect, onClose, onContextMenu }: Props<TId>): JSX.Element;
57
+ export {};
58
+ //# sourceMappingURL=TabBarShell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TabBarShell.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/TabBarShell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAiB,UAAU,EAAE,MAAM,OAAO,CAAC;AAI5D,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGnE,UAAU,KAAK,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM;IACzC;;OAEG;IACH,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAEtC;;OAEG;IACH,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAE5B;;OAEG;IACH,OAAO,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAE3B;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;CACtE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,EACvD,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,gBAAwB,EACxB,OAAe,EACf,WAAW,EACX,aAAa,EACb,gBAAkC,EAClC,cAA0B,EAC1B,QAAQ,EACR,OAAO,EACP,aAAa,EACd,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,OAAO,CA4E1B"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
+ import { TabCloseButton } from '../TabCloseButton/index.js';
3
+ import { tabItem } from '../classes.js';
4
+ import { cn } from '../utils.js';
5
+ import { useSortableTabItem } from './useSortableTabItem.js';
6
+ /**
7
+ * Single document-style tab row with close button and optional drag reordering.
8
+ */
9
+ export function TabBarShell({ item, tabIndex, sortableId, sortableDisabled = false, exiting = false, tabIdPrefix, panelIdPrefix, maxTabWidthClass = 'max-w-[220px]', sortableCursor = 'pointer', onSelect, onClose, onContextMenu }) {
10
+ /**
11
+ * Activates this tab when the user presses Enter or Space on the tab control.
12
+ *
13
+ * @param event - Keyboard event from the tab element.
14
+ */
15
+ const handleTabKeyDown = (event) => {
16
+ if (event.key === 'Enter' || event.key === ' ') {
17
+ event.preventDefault();
18
+ onSelect(item.id);
19
+ }
20
+ };
21
+ const { setNodeRef, listeners, style } = useSortableTabItem(sortableId, sortableDisabled || exiting);
22
+ const showActive = exiting ? false : item.active || Boolean(item.highlighted);
23
+ const cursorClass = exiting || sortableDisabled
24
+ ? 'cursor-pointer'
25
+ : sortableCursor === 'grab'
26
+ ? 'cursor-grab active:cursor-grabbing'
27
+ : 'cursor-pointer';
28
+ return (_jsxs("div", { ref: exiting ? undefined : setNodeRef, style: exiting ? undefined : style, role: "tab", id: exiting ? undefined : `${tabIdPrefix}${String(item.id)}`, "aria-controls": exiting ? undefined : `${panelIdPrefix}${String(item.id)}`, "aria-selected": item.active, "aria-label": item.accessibleName, title: item.title, tabIndex: exiting ? -1 : tabIndex, className: cn('group -mb-1 flex min-h-12 shrink-0 items-stretch gap-2.5 self-stretch rounded-t-lg border border-b-2 px-4', maxTabWidthClass, exiting ? 'pointer-events-none' : cursorClass, tabItem(showActive)), onClick: exiting ? undefined : () => onSelect(item.id), onContextMenu: exiting
29
+ ? undefined
30
+ : (event) => {
31
+ event.preventDefault();
32
+ onContextMenu?.(item.id, event);
33
+ }, onKeyDown: exiting ? undefined : handleTabKeyDown, ...(sortableDisabled || exiting ? {} : listeners), children: [_jsx("span", { className: "app-no-drag flex min-w-0 flex-1 items-center gap-1.5 py-2 text-inherit", children: item.content }), !exiting && (_jsx("span", { className: "app-no-drag flex shrink-0 items-center self-center", onPointerDown: (event) => event.stopPropagation(), children: _jsx(TabCloseButton, { ariaLabel: item.closeAccessibleName, title: item.closeAccessibleName, tabIndex: 0, onClick: (event) => {
34
+ event.stopPropagation();
35
+ onClose(item.id);
36
+ } }) }))] }));
37
+ }
@@ -0,0 +1,32 @@
1
+ import type { JSX } from 'react';
2
+ import type { MenuItem } from '../RowActionsMenu/index.js';
3
+ interface Position {
4
+ /**
5
+ * Viewport X coordinate where the menu was opened.
6
+ */
7
+ x: number;
8
+ /**
9
+ * Viewport Y coordinate where the menu was opened.
10
+ */
11
+ y: number;
12
+ }
13
+ interface Props {
14
+ /**
15
+ * Grouped menu entries. Each inner array is one visual group separated by a divider.
16
+ */
17
+ groups: MenuItem[][];
18
+ /**
19
+ * Cursor position used to anchor the menu panel.
20
+ */
21
+ position: Position;
22
+ /**
23
+ * Called when the menu should close without selecting an item.
24
+ */
25
+ onClose: () => void;
26
+ }
27
+ /**
28
+ * Cursor-positioned context menu for tab bars, rendered in a portal at the click point.
29
+ */
30
+ export declare function TabContextMenu({ groups, position, onClose }: Props): JSX.Element | null;
31
+ export {};
32
+ //# sourceMappingURL=TabContextMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TabContextMenu.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/TabContextMenu.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAiB,MAAM,OAAO,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAI3D,UAAU,QAAQ;IAChB;;OAEG;IACH,CAAC,EAAE,MAAM,CAAC;IAEV;;OAEG;IACH,CAAC,EAAE,MAAM,CAAC;CACX;AAED,UAAU,KAAK;IACb;;OAEG;IACH,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,QAAQ,CAAC;IAEnB;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAgCD;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAkJvF"}
@@ -0,0 +1,133 @@
1
+ import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from '@harborclient/sdk/react';
3
+ import { portalToBody } from '../portalToBody.js';
4
+ import { cn, resolveTabListKeyAction } from '../utils.js';
5
+ /**
6
+ * Tailwind classes for a single menu item button.
7
+ *
8
+ * @param variant - Visual variant for default or destructive actions.
9
+ */
10
+ function menuItemClass(variant) {
11
+ const base = 'block w-full cursor-pointer border-none bg-transparent px-3.5 py-1.5 text-left text-[16px] app-no-drag';
12
+ return variant === 'danger'
13
+ ? `${base} text-text hover:bg-danger/15 hover:text-danger`
14
+ : `${base} text-text hover:bg-selection`;
15
+ }
16
+ /**
17
+ * Clamps a menu position so the panel stays fully inside the viewport.
18
+ *
19
+ * @param position - Requested top-left coordinates.
20
+ * @param size - Measured menu width and height.
21
+ */
22
+ function clampMenuPosition(position, size) {
23
+ const margin = 8;
24
+ const maxX = Math.max(margin, window.innerWidth - size.width - margin);
25
+ const maxY = Math.max(margin, window.innerHeight - size.height - margin);
26
+ return {
27
+ x: Math.min(Math.max(position.x, margin), maxX),
28
+ y: Math.min(Math.max(position.y, margin), maxY)
29
+ };
30
+ }
31
+ /**
32
+ * Cursor-positioned context menu for tab bars, rendered in a portal at the click point.
33
+ */
34
+ export function TabContextMenu({ groups, position, onClose }) {
35
+ const menuRef = useRef(null);
36
+ const itemRefs = useRef([]);
37
+ const [focusedIndex, setFocusedIndex] = useState(0);
38
+ const [clampedPosition, setClampedPosition] = useState(position);
39
+ const flatItems = useMemo(() => groups.flat(), [groups]);
40
+ /**
41
+ * Closes the menu and notifies the parent.
42
+ */
43
+ const closeMenu = useCallback(() => {
44
+ onClose();
45
+ }, [onClose]);
46
+ /**
47
+ * Focuses a menu item by index and updates roving tabindex state.
48
+ *
49
+ * @param index - Flat index into `flatItems`.
50
+ */
51
+ const focusItem = useCallback((index) => {
52
+ setFocusedIndex(index);
53
+ requestAnimationFrame(() => {
54
+ itemRefs.current[index]?.focus();
55
+ });
56
+ }, []);
57
+ /**
58
+ * Re-clamps the menu after mount and focuses the first item once dimensions are known.
59
+ */
60
+ useLayoutEffect(() => {
61
+ const menu = menuRef.current;
62
+ if (!menu) {
63
+ return;
64
+ }
65
+ const rect = menu.getBoundingClientRect();
66
+ setClampedPosition(clampMenuPosition(position, {
67
+ width: rect.width,
68
+ height: rect.height
69
+ }));
70
+ itemRefs.current[0]?.focus();
71
+ }, [position, groups]);
72
+ /**
73
+ * Closes the menu on outside click or Escape while it is open.
74
+ */
75
+ useEffect(() => {
76
+ const handleMouseDown = (event) => {
77
+ if (menuRef.current && !menuRef.current.contains(event.target)) {
78
+ closeMenu();
79
+ }
80
+ };
81
+ const handleKeyDown = (event) => {
82
+ if (event.key === 'Escape') {
83
+ event.preventDefault();
84
+ closeMenu();
85
+ }
86
+ };
87
+ document.addEventListener('mousedown', handleMouseDown);
88
+ document.addEventListener('keydown', handleKeyDown);
89
+ return () => {
90
+ document.removeEventListener('mousedown', handleMouseDown);
91
+ document.removeEventListener('keydown', handleKeyDown);
92
+ };
93
+ }, [closeMenu]);
94
+ /**
95
+ * Handles keyboard navigation within the open menu.
96
+ *
97
+ * @param event - Keyboard event from the menu panel.
98
+ */
99
+ const handleMenuKeyDown = (event) => {
100
+ if (flatItems.length === 0) {
101
+ return;
102
+ }
103
+ if (event.key === 'Tab') {
104
+ closeMenu();
105
+ return;
106
+ }
107
+ const arrowIndex = resolveTabListKeyAction(event.key, focusedIndex, flatItems.length);
108
+ if (arrowIndex !== null) {
109
+ event.preventDefault();
110
+ focusItem(arrowIndex);
111
+ }
112
+ };
113
+ if (flatItems.length === 0) {
114
+ return null;
115
+ }
116
+ return portalToBody(_jsx("div", { ref: menuRef, role: "menu", "aria-label": "Tab actions", className: "hc-tab-context-menu app-no-drag fixed z-50 min-w-[200px] rounded-md border border-separator bg-surface py-1 shadow-md", style: { left: clampedPosition.x, top: clampedPosition.y }, onKeyDown: handleMenuKeyDown, children: groups.map((group, groupIndex) => {
117
+ let flatIndex = groups
118
+ .slice(0, groupIndex)
119
+ .reduce((count, groupItems) => count + groupItems.length, 0);
120
+ return (_jsx("div", { className: groupIndex > 0
121
+ ? 'hc-tab-context-menu-group border-t border-separator'
122
+ : 'hc-tab-context-menu-group', children: group.map((item) => {
123
+ const itemIndex = flatIndex++;
124
+ return (_jsx("button", { ref: (element) => {
125
+ itemRefs.current[itemIndex] = element;
126
+ }, type: "button", role: "menuitem", tabIndex: itemIndex === focusedIndex ? 0 : -1, className: cn('hc-tab-context-menu-item', menuItemClass(item.variant)), onClick: (event) => {
127
+ event.stopPropagation();
128
+ closeMenu();
129
+ item.onSelect();
130
+ }, children: item.label }, item.label));
131
+ }) }, groupIndex));
132
+ }) }));
133
+ }
@@ -0,0 +1,26 @@
1
+ import type { JSX } from 'react';
2
+ interface Props {
3
+ /**
4
+ * Accessible name for the new-tab control.
5
+ */
6
+ ariaLabel: string;
7
+ /**
8
+ * Native tooltip text for the new-tab control.
9
+ */
10
+ title: string;
11
+ /**
12
+ * When true, renders a tab-height slot for wrapped tab lists; otherwise uses
13
+ * the compact circular control for horizontal scroll mode.
14
+ */
15
+ wrapped: boolean;
16
+ /**
17
+ * Called when the user opens a new tab or chat.
18
+ */
19
+ onClick: () => void;
20
+ }
21
+ /**
22
+ * New-tab "+" control shared by request editor and AI chat tab bars.
23
+ */
24
+ export declare function TabNewButton({ ariaLabel, title, wrapped, onClick }: Props): JSX.Element;
25
+ export {};
26
+ //# sourceMappingURL=TabNewButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TabNewButton.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/TabNewButton.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAKjC,UAAU,KAAK;IACb;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAiCvF"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
2
+ import { faPlus } from '@fortawesome/free-solid-svg-icons';
3
+ import { FaIcon } from '../FaIcon/index.js';
4
+ import { tabItem } from '../classes.js';
5
+ import { cn } from '../utils.js';
6
+ /**
7
+ * New-tab "+" control shared by request editor and AI chat tab bars.
8
+ */
9
+ export function TabNewButton({ ariaLabel, title, wrapped, onClick }) {
10
+ if (wrapped) {
11
+ return (_jsx("div", { className: "flex shrink-0", children: _jsx("button", { type: "button", className: cn('app-no-drag -mb-1 inline-flex min-h-12 shrink-0 cursor-pointer items-center justify-center self-end rounded-t-lg border border-b-2 px-3 text-[14px]', tabItem(false)), title: title, "aria-label": ariaLabel, onClick: onClick, children: _jsx(FaIcon, { icon: faPlus, className: "h-3.5 w-3.5" }) }) }));
12
+ }
13
+ return (_jsx("div", { className: "-mb-1 flex shrink-0 items-end px-1", children: _jsx("button", { type: "button", className: "hc-tab-new-button app-no-drag mb-2.5 inline-flex shrink-0 cursor-pointer items-center justify-center border-none bg-transparent text-[14px] text-muted hover:bg-selection hover:text-text focus-visible:bg-selection focus-visible:text-text", title: title, "aria-label": ariaLabel, onClick: onClick, children: _jsx(FaIcon, { icon: faPlus, className: "h-3.5 w-3.5" }) }) }));
14
+ }
@@ -0,0 +1,92 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ import type { MenuItem } from '../RowActionsMenu/index.js';
3
+ import type { TabBarItem, TabBarNewTab, TabBarSortableCursor } from './types.js';
4
+ interface Props<TId extends string | number> {
5
+ /**
6
+ * Open tabs to render in display order.
7
+ */
8
+ tabs: TabBarItem<TId>[];
9
+ /**
10
+ * ID of the currently active tab.
11
+ */
12
+ activeId: TId;
13
+ /**
14
+ * When true, tabs wrap to multiple rows; otherwise they scroll horizontally.
15
+ */
16
+ wrap: boolean;
17
+ /**
18
+ * Accessible name for the tab list.
19
+ */
20
+ ariaLabel: string;
21
+ /**
22
+ * Prefix for tab element ids (for example `request-tab-`).
23
+ */
24
+ tabIdPrefix: string;
25
+ /**
26
+ * Prefix for linked tab panel ids (for example `request-tabpanel-`).
27
+ */
28
+ panelIdPrefix: string;
29
+ /**
30
+ * Prefix for dnd-kit sortable ids. Defaults to `${tabIdPrefix}sort:`.
31
+ */
32
+ sortablePrefix?: string;
33
+ /**
34
+ * Configuration for the new-tab control.
35
+ */
36
+ newTab: TabBarNewTab;
37
+ /**
38
+ * Called when the user selects a tab.
39
+ */
40
+ onSelect: (id: TId) => void;
41
+ /**
42
+ * Called when the user closes a tab.
43
+ */
44
+ onClose: (id: TId) => void;
45
+ /**
46
+ * Persists a new tab order after drag-and-drop reordering.
47
+ */
48
+ onReorder: (orderedIds: TId[]) => void;
49
+ /**
50
+ * Builds grouped context menu entries when a tab is right-clicked.
51
+ */
52
+ buildContextMenuGroups?: (targetId: TId, orderedIds: TId[]) => MenuItem[][];
53
+ /**
54
+ * When ArrowDown is pressed on a focused tab, returns true if focus moved into
55
+ * the linked panel (for example request editor content).
56
+ */
57
+ onArrowDownIntoPanel?: (id: TId) => boolean;
58
+ /**
59
+ * Focuses the tab control for keyboard navigation and focus restoration.
60
+ */
61
+ onFocusTab?: (id: TId) => void;
62
+ /**
63
+ * Wraps the tab row for horizontal scrolling. Defaults to overflow-x-auto.
64
+ */
65
+ renderScrollContainer?: (row: ReactNode) => ReactNode;
66
+ /**
67
+ * Maximum width class applied to each tab shell.
68
+ */
69
+ maxTabWidthClass?: string;
70
+ /**
71
+ * Cursor style when sortable drag is enabled.
72
+ */
73
+ sortableCursor?: TabBarSortableCursor;
74
+ /**
75
+ * Additional classes for the outer tab bar container.
76
+ */
77
+ className?: string;
78
+ }
79
+ /**
80
+ * Document-style tab bar with drag reorder, keyboard navigation, close animation,
81
+ * optional context menu, and a new-tab control.
82
+ */
83
+ export declare function TabBar<TId extends string | number>({ tabs, activeId, wrap, ariaLabel, tabIdPrefix, panelIdPrefix, sortablePrefix, newTab, onSelect, onClose, onReorder, buildContextMenuGroups, onArrowDownIntoPanel, onFocusTab, renderScrollContainer, maxTabWidthClass, sortableCursor, className }: Props<TId>): JSX.Element;
84
+ export type { TabBarItem, TabBarNewTab, TabBarSortableCursor } from './types.js';
85
+ export { TabNewButton } from './TabNewButton.js';
86
+ export { TabContextMenu } from './TabContextMenu.js';
87
+ export { ClosingTabShell } from './ClosingTabShell.js';
88
+ export { TabBarShell } from './TabBarShell.js';
89
+ export { useSortableTabItem } from './useSortableTabItem.js';
90
+ export { useExitingTabItems, detectRemovedTabItems, resolveInsertBeforeId, type ExitingTabItem } from './useExitingTabItems.js';
91
+ export { buildTabCloseMenuGroups, tabIdsToCloseOthers, tabIdsToCloseToTheRight } from './tabCloseMenuHelpers.js';
92
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAkB,GAAG,EAA6B,SAAS,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAM3D,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAgCjF,UAAU,KAAK,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM;IACzC;;OAEG;IACH,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;IAExB;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAC;IAEd;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IAEd;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,MAAM,EAAE,YAAY,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAE5B;;OAEG;IACH,OAAO,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAE3B;;OAEG;IACH,SAAS,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAEvC;;OAEG;IACH,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,EAAE,CAAC;IAE5E;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC;IAE5C;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAE/B;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,SAAS,CAAC;IAEtD;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAEtC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqDD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,EAClD,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,WAAW,EACX,aAAa,EACb,cAAc,EACd,MAAM,EACN,QAAQ,EACR,OAAO,EACP,SAAS,EACT,sBAAsB,EACtB,oBAAoB,EACpB,UAAU,EACV,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACd,SAAS,EACV,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,OAAO,CAqT1B;AAED,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,0BAA0B,CAAC"}