@paroicms/react-ui 0.5.0 → 0.5.1
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.
- package/dist/Dialog.d.ts +2 -2
- package/dist/Dialog.js +23 -27
- package/dist/MultiSelect.d.ts +2 -1
- package/dist/MultiSelect.js +24 -21
- package/dist/PopupMenu.d.ts +3 -4
- package/dist/PopupMenu.js +13 -51
- package/dist/SplitButton.d.ts +1 -0
- package/dist/SplitButton.js +12 -15
- package/dist/Tooltip.d.ts +1 -1
- package/dist/Tooltip.js +11 -32
- package/dist/alert-stack.d.ts +3 -1
- package/dist/alert-stack.js +2 -2
- package/dist/popup-positioning.d.ts +10 -0
- package/dist/popup-positioning.js +160 -0
- package/package.json +1 -1
- package/styles/Alert.css +1 -0
- package/styles/Dialog.css +32 -21
- package/styles/MultiSelect.css +8 -23
- package/styles/SplitButton.css +2 -8
- package/styles/Tooltip.css +4 -18
- package/styles/Tree.css +4 -3
package/dist/Dialog.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export interface DialogProps {
|
|
|
8
8
|
children: ReactNode;
|
|
9
9
|
className?: string;
|
|
10
10
|
closable?: boolean;
|
|
11
|
-
|
|
11
|
+
size?: "sm" | "md" | "lg";
|
|
12
12
|
}
|
|
13
|
-
export declare function Dialog({ visible, onHide, header, footer, children, className, closable,
|
|
13
|
+
export declare function Dialog({ visible, onHide, header, footer, children, className, closable, size, }: DialogProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/Dialog.js
CHANGED
|
@@ -3,39 +3,38 @@ import "../styles/Dialog.css";
|
|
|
3
3
|
import { clsx } from "clsx";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useEffect, useRef } from "react";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export function Dialog({ visible, onHide, header, footer, children, className, closable = true, modal = true, }) {
|
|
10
|
-
// Track this dialog's position in the stack for ESC key handling
|
|
11
|
-
const dialogIdRef = useRef(undefined);
|
|
6
|
+
export function Dialog({ visible, onHide, header, footer, children, className, closable = true, size, }) {
|
|
7
|
+
const dialogRef = useRef(null);
|
|
8
|
+
// Control open/close with native methods
|
|
12
9
|
useEffect(() => {
|
|
10
|
+
const dialog = dialogRef.current;
|
|
11
|
+
if (!dialog)
|
|
12
|
+
return;
|
|
13
13
|
if (visible) {
|
|
14
|
-
|
|
14
|
+
if (!dialog.open) {
|
|
15
|
+
dialog.showModal();
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
if (dialogIdRef.current === dialogStackCounter) {
|
|
20
|
-
dialogStackCounter--;
|
|
21
|
-
}
|
|
22
|
-
dialogIdRef.current = undefined;
|
|
18
|
+
else {
|
|
19
|
+
if (dialog.open) {
|
|
20
|
+
dialog.close();
|
|
23
21
|
}
|
|
24
|
-
}
|
|
22
|
+
}
|
|
25
23
|
}, [visible]);
|
|
26
|
-
// Handle
|
|
24
|
+
// Handle ESC key via native cancel event
|
|
27
25
|
useEffect(() => {
|
|
28
|
-
|
|
26
|
+
const dialog = dialogRef.current;
|
|
27
|
+
if (!dialog)
|
|
29
28
|
return;
|
|
30
|
-
const
|
|
31
|
-
//
|
|
32
|
-
if (
|
|
29
|
+
const handleCancel = (e) => {
|
|
30
|
+
e.preventDefault(); // Prevent default close
|
|
31
|
+
if (closable) {
|
|
33
32
|
onHide();
|
|
34
33
|
}
|
|
35
34
|
};
|
|
36
|
-
|
|
37
|
-
return () =>
|
|
38
|
-
}, [
|
|
35
|
+
dialog.addEventListener("cancel", handleCancel);
|
|
36
|
+
return () => dialog.removeEventListener("cancel", handleCancel);
|
|
37
|
+
}, [closable, onHide]);
|
|
39
38
|
// Prevent body scroll when open
|
|
40
39
|
useEffect(() => {
|
|
41
40
|
if (visible) {
|
|
@@ -48,8 +47,5 @@ export function Dialog({ visible, onHide, header, footer, children, className, c
|
|
|
48
47
|
document.body.style.overflow = "";
|
|
49
48
|
};
|
|
50
49
|
}, [visible]);
|
|
51
|
-
|
|
52
|
-
return null;
|
|
53
|
-
const dialog = (_jsxs("div", { className: "PaDialog-overlay", children: [modal && _jsx("div", { className: "PaDialog-backdrop", onClick: closable ? onHide : undefined }), _jsxs("div", { className: clsx("PaDialog", className), role: "dialog", "aria-modal": modal, children: [(header || closable) && (_jsxs("div", { className: "PaDialog-header", children: [header && _jsx("div", { className: "PaDialog-title", children: header }), closable && (_jsx("button", { type: "button", className: "PaDialog-close", onClick: onHide, "aria-label": "Close", children: _jsx(X, { size: 18 }) }))] })), _jsx("div", { className: "PaDialog-content", children: children }), footer && _jsx("div", { className: "PaDialog-footer", children: footer })] })] }));
|
|
54
|
-
return createPortal(dialog, document.body);
|
|
50
|
+
return (_jsxs("dialog", { ref: dialogRef, className: clsx("PaDialog", size && `size-${size}`, className), children: [(header || closable) && (_jsxs("div", { className: "PaDialog-header", children: [header && _jsx("div", { className: "PaDialog-title", children: header }), closable && (_jsx("button", { type: "button", className: "PaDialog-close", onClick: onHide, "aria-label": "Close", children: _jsx(X, { size: 18 }) }))] })), _jsx("div", { className: "PaDialog-content", children: children }), footer && _jsx("div", { className: "PaDialog-footer", children: footer })] }));
|
|
55
51
|
}
|
package/dist/MultiSelect.d.ts
CHANGED
|
@@ -13,5 +13,6 @@ export interface MultiSelectProps {
|
|
|
13
13
|
className?: string;
|
|
14
14
|
placeholder?: string;
|
|
15
15
|
disabled?: boolean;
|
|
16
|
+
position?: "auto" | "top" | "bottom";
|
|
16
17
|
}
|
|
17
|
-
export declare function MultiSelect({ value, onChange, options, label, error, className, placeholder, disabled, }: MultiSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function MultiSelect({ value, onChange, options, label, error, className, placeholder, disabled, position, }: MultiSelectProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/MultiSelect.js
CHANGED
|
@@ -3,21 +3,31 @@ import "../styles/MultiSelect.css";
|
|
|
3
3
|
import { clsx } from "clsx";
|
|
4
4
|
import { ChevronDown, X } from "lucide-react";
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
|
-
|
|
6
|
+
import { Checkbox } from "./Checkbox.js";
|
|
7
|
+
import { computePopupPosition, setupPopoverPositioning } from "./popup-positioning.js";
|
|
8
|
+
export function MultiSelect({ value, onChange, options, label, error, className, placeholder, disabled, position, }) {
|
|
7
9
|
const [open, setOpen] = useState(false);
|
|
8
10
|
const containerRef = useRef(null);
|
|
9
|
-
|
|
11
|
+
const dropdownRef = useRef(null);
|
|
12
|
+
const dropdownId = useRef(`pa-multiselect-${Math.random().toString(36).substring(2, 9)}`);
|
|
10
13
|
useEffect(() => {
|
|
11
|
-
|
|
14
|
+
const dropdown = dropdownRef.current;
|
|
15
|
+
const container = containerRef.current;
|
|
16
|
+
if (!dropdown || !container)
|
|
12
17
|
return;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
setOpen(false);
|
|
16
|
-
}
|
|
18
|
+
const handleToggle = (e) => {
|
|
19
|
+
setOpen(e.newState === "open");
|
|
17
20
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
dropdown.addEventListener("toggle", handleToggle);
|
|
22
|
+
const cleanupPositioning = setupPopoverPositioning(dropdown, () => {
|
|
23
|
+
dropdown.style.width = `${container.offsetWidth}px`;
|
|
24
|
+
return computePopupPosition(container, dropdown, position ?? "auto", "vertical");
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
dropdown.removeEventListener("toggle", handleToggle);
|
|
28
|
+
cleanupPositioning();
|
|
29
|
+
};
|
|
30
|
+
}, [position]);
|
|
21
31
|
const toggleOption = (optionValue) => {
|
|
22
32
|
if (value.includes(optionValue)) {
|
|
23
33
|
onChange(value.filter((v) => v !== optionValue));
|
|
@@ -30,21 +40,14 @@ export function MultiSelect({ value, onChange, options, label, error, className,
|
|
|
30
40
|
onChange(value.filter((v) => v !== optionValue));
|
|
31
41
|
};
|
|
32
42
|
const selectedOptions = options.filter((opt) => value.includes(opt.value));
|
|
33
|
-
const controlElement = (_jsxs(_Fragment, { children: [_jsxs("span", { className:
|
|
34
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
43
|
+
const controlElement = (_jsxs(_Fragment, { children: [_jsxs("span", { className: "PaMultiSelect-control", onClick: () => !disabled && dropdownRef.current?.togglePopover(), onKeyDown: (e) => {
|
|
44
|
+
if ((e.key === "Enter" || e.key === " ") && !disabled) {
|
|
35
45
|
e.preventDefault();
|
|
36
|
-
|
|
37
|
-
setOpen(!open);
|
|
46
|
+
dropdownRef.current?.togglePopover();
|
|
38
47
|
}
|
|
39
48
|
}, tabIndex: disabled ? -1 : 0, role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", children: [_jsx("span", { className: "PaMultiSelect-values", children: selectedOptions.length > 0 ? (selectedOptions.map((opt) => (_jsxs("span", { className: "PaMultiSelect-chip", children: [opt.label, _jsx("button", { type: "button", className: "PaMultiSelect-chipRemove", onClick: (e) => {
|
|
40
49
|
e.stopPropagation();
|
|
41
50
|
removeValue(opt.value);
|
|
42
|
-
}, "aria-label": `Remove ${opt.label}`, children: _jsx(X, { size: 12 }) })] }, opt.value)))) : (_jsx("span", { className: "PaMultiSelect-placeholder", children: placeholder })) }), _jsx(ChevronDown, { className: "PaMultiSelect-icon", size: 16 })] }),
|
|
43
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
44
|
-
e.preventDefault();
|
|
45
|
-
if (!option.disabled)
|
|
46
|
-
toggleOption(option.value);
|
|
47
|
-
}
|
|
48
|
-
}, tabIndex: option.disabled ? -1 : 0, role: "option", "aria-selected": value.includes(option.value), children: [_jsx("span", { className: "PaMultiSelect-checkbox", children: value.includes(option.value) && "✓" }), option.label] }, option.value))) }))] }));
|
|
51
|
+
}, "aria-label": `Remove ${opt.label}`, children: _jsx(X, { size: 12 }) })] }, opt.value)))) : (_jsx("span", { className: "PaMultiSelect-placeholder", children: placeholder })) }), _jsx(ChevronDown, { className: "PaMultiSelect-icon", size: 16 })] }), _jsx("span", { ref: dropdownRef, id: dropdownId.current, className: "PaMultiSelect-dropdown", role: "listbox", popover: "auto", children: options.map((option) => (_jsxs("label", { className: clsx("PaMultiSelect-option", value.includes(option.value) && "selected", option.disabled && "disabled"), children: [_jsx(Checkbox, { checked: value.includes(option.value), onChange: () => toggleOption(option.value), disabled: option.disabled }), option.label] }, option.value))) })] }));
|
|
49
52
|
return (_jsxs("span", { ref: containerRef, className: clsx("PaMultiSelect", error && "error", disabled && "disabled", className), children: [label ? (_jsxs("label", { className: "PaMultiSelect-wrapper", children: [_jsx("span", { className: "PaMultiSelect-label", children: label }), controlElement] })) : (controlElement), error && _jsx("span", { className: "PaMultiSelect-error", children: error })] }));
|
|
50
53
|
}
|
package/dist/PopupMenu.d.ts
CHANGED
|
@@ -5,9 +5,8 @@ export interface PopupMenuProps {
|
|
|
5
5
|
items: MenuNode[];
|
|
6
6
|
className?: string;
|
|
7
7
|
id?: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
position?: "bottom" | "top";
|
|
8
|
+
/** Position of the popup relative to the trigger. Default is 'auto'. */
|
|
9
|
+
position?: "auto" | "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
|
11
10
|
/** Optional custom trigger element. If provided, it will be used instead of the default button. */
|
|
12
11
|
children?: ReactNode;
|
|
13
12
|
ref?: Ref<PopupMenuRef>;
|
|
@@ -17,4 +16,4 @@ export interface PopupMenuRef {
|
|
|
17
16
|
show: (event: MouseEvent) => void;
|
|
18
17
|
hide: () => void;
|
|
19
18
|
}
|
|
20
|
-
export declare function PopupMenu({ items, className, id,
|
|
19
|
+
export declare function PopupMenu({ items, className, id, position, children, ref, }: PopupMenuProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/PopupMenu.js
CHANGED
|
@@ -5,28 +5,13 @@ import { ChevronRight, ChevronsDown } from "lucide-react";
|
|
|
5
5
|
import { cloneElement, isValidElement, useEffect, useImperativeHandle, useRef, } from "react";
|
|
6
6
|
import { Button } from "./Button.js";
|
|
7
7
|
import { MenuItem } from "./MenuItem.js";
|
|
8
|
+
import { computePopupPosition, setupPopoverPositioning } from "./popup-positioning.js";
|
|
8
9
|
const isPopoverSupported = typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
|
|
9
10
|
if (!isPopoverSupported) {
|
|
10
11
|
console?.error("PopupMenu: Popover API is not supported in this browser.");
|
|
11
12
|
}
|
|
12
13
|
let seq = 0;
|
|
13
|
-
|
|
14
|
-
* Utility function to configure popup positioning
|
|
15
|
-
*/
|
|
16
|
-
function setupPopoverPositioning(element, getPosition) {
|
|
17
|
-
const handleToggle = (e) => {
|
|
18
|
-
const newState = e.newState;
|
|
19
|
-
if (newState === "open") {
|
|
20
|
-
const position = getPosition();
|
|
21
|
-
element.style.position = "absolute";
|
|
22
|
-
element.style.top = `${position.top}px`;
|
|
23
|
-
element.style.left = `${position.left}px`;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
element.addEventListener("toggle", handleToggle);
|
|
27
|
-
return () => element.removeEventListener("toggle", handleToggle);
|
|
28
|
-
}
|
|
29
|
-
export function PopupMenu({ items, className, id, autoPosition = true, position = "bottom", children, ref, }) {
|
|
14
|
+
export function PopupMenu({ items, className, id, position = "auto", children, ref, }) {
|
|
30
15
|
const menuRef = useRef(null);
|
|
31
16
|
const triggerRef = useRef(null);
|
|
32
17
|
const menuIdRef = useRef(undefined);
|
|
@@ -55,28 +40,12 @@ export function PopupMenu({ items, className, id, autoPosition = true, position
|
|
|
55
40
|
}));
|
|
56
41
|
// Position popover when it opens
|
|
57
42
|
useEffect(() => {
|
|
58
|
-
if (!autoPosition || !menuRef.current || !triggerRef.current)
|
|
59
|
-
return;
|
|
60
43
|
const menuEl = menuRef.current;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Position above the trigger
|
|
67
|
-
const menuHeight = menuEl.offsetHeight || 150; // Fallback height
|
|
68
|
-
return {
|
|
69
|
-
top: rect.top + window.scrollY - menuHeight - 8,
|
|
70
|
-
left: rect.left + window.scrollX,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
// Default: position below
|
|
74
|
-
return {
|
|
75
|
-
top: rect.bottom + window.scrollY,
|
|
76
|
-
left: rect.left + window.scrollX,
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
}, [autoPosition, position]);
|
|
44
|
+
const triggerEl = triggerRef.current;
|
|
45
|
+
if (!menuEl || !triggerEl)
|
|
46
|
+
return;
|
|
47
|
+
return setupPopoverPositioning(menuEl, () => computePopupPosition(triggerEl, menuEl, position, "corner"));
|
|
48
|
+
}, [position]);
|
|
80
49
|
const triggerProps = {
|
|
81
50
|
popoverTarget: menuIdRef.current,
|
|
82
51
|
popoverTargetAction: "toggle",
|
|
@@ -125,20 +94,13 @@ function PopupMenuItemWrapper({ item, onHide }) {
|
|
|
125
94
|
}
|
|
126
95
|
// Handle submenu positioning with Popover API
|
|
127
96
|
useEffect(() => {
|
|
128
|
-
if (!subMenuRef.current || !item.subMenu)
|
|
129
|
-
return;
|
|
130
97
|
const subMenuEl = subMenuRef.current;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
left: rect.right + 5, // 5px gap
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
return { top: 0, left: 0 };
|
|
141
|
-
});
|
|
98
|
+
if (!subMenuEl || !item.subMenu)
|
|
99
|
+
return;
|
|
100
|
+
const parentEl = subMenuEl.parentElement;
|
|
101
|
+
if (!parentEl)
|
|
102
|
+
return;
|
|
103
|
+
return setupPopoverPositioning(subMenuEl, () => computePopupPosition(parentEl, subMenuEl, "right", "cardinal"));
|
|
142
104
|
}, [item.subMenu]);
|
|
143
105
|
return (_jsxs("div", { className: "PaPopupMenu-item", popoverTargetAction: "toggle", popoverTarget: subMenuId.current, children: [_jsx(MenuItem, { item: itemWithSubmenuHandling }), item.subMenu && (_jsx("div", { className: "PaPopupMenu-submenu", popover: "auto", id: subMenuId.current, ref: subMenuRef, children: item.subMenu.map((subItem) => (_jsx(PopupMenuItemWrapper, { item: subItem, onHide: onHide }, subItem.key))) }))] }));
|
|
144
106
|
}
|
package/dist/SplitButton.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface SplitAnchorElProps extends SplitButtonBaseProps, Omit<AnchorElP
|
|
|
11
11
|
interface SplitButtonBaseProps {
|
|
12
12
|
items: SplitButtonItem[];
|
|
13
13
|
className?: string;
|
|
14
|
+
position?: "auto" | "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
|
14
15
|
}
|
|
15
16
|
export interface SplitButtonItem {
|
|
16
17
|
label: string;
|
package/dist/SplitButton.js
CHANGED
|
@@ -2,26 +2,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import "../styles/SplitButton.css";
|
|
3
3
|
import { clsx } from "clsx";
|
|
4
4
|
import { ChevronDown } from "lucide-react";
|
|
5
|
-
import { useEffect, useRef
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
6
|
import { Button, } from "./Button.js";
|
|
7
|
+
import { computePopupPosition, setupPopoverPositioning } from "./popup-positioning.js";
|
|
7
8
|
export function SplitButton(props) {
|
|
8
|
-
const { items, className, ...buttonProps } = props;
|
|
9
|
-
const [open, setOpen] = useState(false);
|
|
9
|
+
const { items, className, position, ...buttonProps } = props;
|
|
10
10
|
const containerRef = useRef(null);
|
|
11
|
+
const menuRef = useRef(null);
|
|
12
|
+
const menuId = useRef(`pa-splitbtn-menu-${Math.random().toString(36).substring(2, 9)}`);
|
|
11
13
|
const severity = props.severity ?? "primary";
|
|
12
14
|
const disabled = props.disabled;
|
|
13
|
-
// Close dropdown when clicking outside
|
|
14
15
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
+
const menu = menuRef.current;
|
|
17
|
+
const container = containerRef.current;
|
|
18
|
+
if (!menu || !container)
|
|
16
19
|
return;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
setOpen(false);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
23
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
24
|
-
}, [open]);
|
|
20
|
+
return setupPopoverPositioning(menu, () => computePopupPosition(container, menu, position ?? "auto", "corner"));
|
|
21
|
+
}, [position]);
|
|
25
22
|
const handleItemClick = async (item) => {
|
|
26
23
|
try {
|
|
27
24
|
await item.command();
|
|
@@ -35,8 +32,8 @@ export function SplitButton(props) {
|
|
|
35
32
|
}
|
|
36
33
|
}
|
|
37
34
|
finally {
|
|
38
|
-
|
|
35
|
+
menuRef.current?.hidePopover();
|
|
39
36
|
}
|
|
40
37
|
};
|
|
41
|
-
return (_jsxs("div", { ref: containerRef, className: clsx("PaSplitBtn", severity,
|
|
38
|
+
return (_jsxs("div", { ref: containerRef, className: clsx("PaSplitBtn", severity, disabled && "disabled", className), children: [_jsx(Button, { ...buttonProps, className: "PaSplitBtn-main" }), _jsx("button", { type: "button", className: "PaSplitBtn-toggle", popoverTarget: menuId.current, popoverTargetAction: "toggle", "aria-haspopup": "menu", children: _jsx(ChevronDown, { className: "PaSplitBtn-toggleIcon" }) }), _jsx("div", { ref: menuRef, id: menuId.current, className: "PaSplitBtn-menu", role: "menu", popover: "auto", children: items.map((item) => (_jsxs("button", { type: "button", className: clsx("PaSplitBtn-menuItem", item.danger && "danger", item.disabled && "disabled"), onClick: () => void handleItemClick(item), role: "menuitem", disabled: item.disabled, children: [item.icon && _jsx("span", { className: "PaSplitBtn-menuIcon", children: item.icon }), item.label] }, item.label))) })] }));
|
|
42
39
|
}
|
package/dist/Tooltip.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import "../styles/Tooltip.css";
|
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
3
|
export interface TooltipProps {
|
|
4
4
|
content: string;
|
|
5
|
-
position?: "top" | "bottom" | "left" | "right";
|
|
5
|
+
position?: "auto" | "top" | "bottom" | "left" | "right";
|
|
6
6
|
children: ReactNode;
|
|
7
7
|
className?: string;
|
|
8
8
|
delay?: number;
|
package/dist/Tooltip.js
CHANGED
|
@@ -1,50 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import "../styles/Tooltip.css";
|
|
3
3
|
import { clsx } from "clsx";
|
|
4
|
-
import { useEffect, useRef
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const [coords, setCoords] = useState({ top: 0, left: 0 });
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { computePopupPosition, setupPopoverPositioning } from "./popup-positioning.js";
|
|
6
|
+
export function Tooltip({ content, position = "auto", children, className, delay = 200, }) {
|
|
8
7
|
const triggerRef = useRef(null);
|
|
9
8
|
const tooltipRef = useRef(null);
|
|
10
9
|
const timeoutRef = useRef(undefined);
|
|
11
10
|
const showTooltip = () => {
|
|
12
11
|
timeoutRef.current = window.setTimeout(() => {
|
|
13
|
-
|
|
12
|
+
tooltipRef.current?.showPopover();
|
|
14
13
|
}, delay);
|
|
15
14
|
};
|
|
16
15
|
const hideTooltip = () => {
|
|
17
16
|
if (timeoutRef.current) {
|
|
18
17
|
clearTimeout(timeoutRef.current);
|
|
19
18
|
}
|
|
20
|
-
|
|
19
|
+
tooltipRef.current?.hidePopover();
|
|
21
20
|
};
|
|
22
21
|
useEffect(() => {
|
|
23
|
-
if (!
|
|
22
|
+
if (!tooltipRef.current || !triggerRef.current)
|
|
24
23
|
return;
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
case "top":
|
|
31
|
-
top = rect.top - 8;
|
|
32
|
-
left = rect.left + rect.width / 2;
|
|
33
|
-
break;
|
|
34
|
-
case "bottom":
|
|
35
|
-
top = rect.bottom + 8;
|
|
36
|
-
left = rect.left + rect.width / 2;
|
|
37
|
-
break;
|
|
38
|
-
case "left":
|
|
39
|
-
top = rect.top + rect.height / 2;
|
|
40
|
-
left = rect.left - 8;
|
|
41
|
-
break;
|
|
42
|
-
case "right":
|
|
43
|
-
top = rect.top + rect.height / 2;
|
|
44
|
-
left = rect.right + 8;
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
setCoords({ top, left });
|
|
48
|
-
}, [visible, position]);
|
|
49
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, className: "PaTooltip-trigger", onMouseEnter: showTooltip, onMouseLeave: hideTooltip, onFocus: showTooltip, onBlur: hideTooltip, children: children }), visible && (_jsx("div", { ref: tooltipRef, className: clsx("PaTooltip", position, className), style: { top: coords.top, left: coords.left }, role: "tooltip", children: content }))] }));
|
|
24
|
+
const triggerEl = triggerRef.current;
|
|
25
|
+
const popupEl = tooltipRef.current;
|
|
26
|
+
return setupPopoverPositioning(popupEl, () => computePopupPosition(triggerEl, popupEl, position, "cardinal"));
|
|
27
|
+
}, [position]);
|
|
28
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, className: "PaTooltip-trigger", onMouseEnter: showTooltip, onMouseLeave: hideTooltip, onFocus: showTooltip, onBlur: hideTooltip, children: children }), _jsx("div", { ref: tooltipRef, className: clsx("PaTooltip", className), role: "tooltip", popover: "hint", children: content })] }));
|
|
50
29
|
}
|
package/dist/alert-stack.d.ts
CHANGED
|
@@ -14,5 +14,7 @@ interface AlertStackProviderProps {
|
|
|
14
14
|
}
|
|
15
15
|
export declare function AlertStackProvider({ children, ref }: AlertStackProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
16
16
|
export declare function useAlertStack(): AlertStackHandle;
|
|
17
|
-
export declare function AlertStack(
|
|
17
|
+
export declare function AlertStack(props?: {
|
|
18
|
+
className?: string;
|
|
19
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
18
20
|
export {};
|
package/dist/alert-stack.js
CHANGED
|
@@ -47,7 +47,7 @@ export function useAlertStack() {
|
|
|
47
47
|
}, [context.showAlert]);
|
|
48
48
|
return { showAlert: context.showAlert, showError };
|
|
49
49
|
}
|
|
50
|
-
export function AlertStack() {
|
|
50
|
+
export function AlertStack(props) {
|
|
51
51
|
const context = useContext(AlertStackContext);
|
|
52
52
|
const config = useReactUIConfig();
|
|
53
53
|
if (!context) {
|
|
@@ -68,5 +68,5 @@ export function AlertStack() {
|
|
|
68
68
|
]);
|
|
69
69
|
if (!currentAlert)
|
|
70
70
|
return null;
|
|
71
|
-
return (_jsx(Alert, { severity: currentAlert.severity, onClose: dismissCurrent, children: currentAlert.message }));
|
|
71
|
+
return (_jsx(Alert, { className: props?.className, severity: currentAlert.severity, onClose: dismissCurrent, children: currentAlert.message }));
|
|
72
72
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type CornerPosition = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
|
2
|
+
export type VerticalPosition = "top" | "bottom";
|
|
3
|
+
export type CardinalPosition = "top" | "bottom" | "left" | "right";
|
|
4
|
+
export interface PositionResult {
|
|
5
|
+
top: number;
|
|
6
|
+
left: number;
|
|
7
|
+
position: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function setupPopoverPositioning(popupEl: HTMLElement, getPosition: () => PositionResult): () => void;
|
|
10
|
+
export declare function computePopupPosition(triggerEl: HTMLElement, popupEl: HTMLElement, preferredPosition: string, mode: "corner" | "vertical" | "cardinal"): PositionResult;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export function setupPopoverPositioning(popupEl, getPosition) {
|
|
2
|
+
const handleToggle = (e) => {
|
|
3
|
+
const newState = e.newState;
|
|
4
|
+
if (newState === "open") {
|
|
5
|
+
const position = getPosition();
|
|
6
|
+
popupEl.style.position = "absolute";
|
|
7
|
+
popupEl.style.top = `${position.top}px`;
|
|
8
|
+
popupEl.style.left = `${position.left}px`;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
popupEl.addEventListener("toggle", handleToggle);
|
|
12
|
+
return () => popupEl.removeEventListener("toggle", handleToggle);
|
|
13
|
+
}
|
|
14
|
+
export function computePopupPosition(triggerEl, popupEl, preferredPosition, mode) {
|
|
15
|
+
const triggerRect = triggerEl.getBoundingClientRect();
|
|
16
|
+
const popupWidth = popupEl.offsetWidth;
|
|
17
|
+
const popupHeight = popupEl.offsetHeight;
|
|
18
|
+
const bounds = getConstrainingBounds(triggerEl);
|
|
19
|
+
let result;
|
|
20
|
+
switch (mode) {
|
|
21
|
+
case "corner":
|
|
22
|
+
result = computeCornerPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition);
|
|
23
|
+
break;
|
|
24
|
+
case "vertical":
|
|
25
|
+
result = computeVerticalPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition);
|
|
26
|
+
break;
|
|
27
|
+
case "cardinal":
|
|
28
|
+
result = computeCardinalPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
position: result.position,
|
|
33
|
+
top: result.top + window.scrollY,
|
|
34
|
+
left: result.left + window.scrollX,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function getConstrainingBounds(triggerEl) {
|
|
38
|
+
const dialog = triggerEl.closest("dialog");
|
|
39
|
+
if (dialog) {
|
|
40
|
+
const rect = dialog.getBoundingClientRect();
|
|
41
|
+
return {
|
|
42
|
+
top: rect.top,
|
|
43
|
+
left: rect.left,
|
|
44
|
+
right: rect.right,
|
|
45
|
+
bottom: rect.bottom,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
top: 0,
|
|
50
|
+
left: 0,
|
|
51
|
+
right: window.innerWidth,
|
|
52
|
+
bottom: window.innerHeight,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function computeCornerPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition) {
|
|
56
|
+
const spaceTop = triggerRect.top - bounds.top;
|
|
57
|
+
const spaceBottom = bounds.bottom - triggerRect.bottom;
|
|
58
|
+
const spaceLeft = triggerRect.left - bounds.left;
|
|
59
|
+
const spaceRight = bounds.right - triggerRect.right;
|
|
60
|
+
let position;
|
|
61
|
+
if (preferredPosition !== "auto") {
|
|
62
|
+
position = preferredPosition;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Pick corner with most space, prefer bottom-right > bottom-left > top-right > top-left
|
|
66
|
+
const canBottom = spaceBottom >= popupHeight;
|
|
67
|
+
const canTop = spaceTop >= popupHeight;
|
|
68
|
+
const canRight = spaceRight + triggerRect.width >= popupWidth;
|
|
69
|
+
const canLeft = spaceLeft + triggerRect.width >= popupWidth;
|
|
70
|
+
if (canBottom && canRight) {
|
|
71
|
+
position = "bottomRight";
|
|
72
|
+
}
|
|
73
|
+
else if (canBottom && canLeft) {
|
|
74
|
+
position = "bottomLeft";
|
|
75
|
+
}
|
|
76
|
+
else if (canTop && canRight) {
|
|
77
|
+
position = "topRight";
|
|
78
|
+
}
|
|
79
|
+
else if (canTop && canLeft) {
|
|
80
|
+
position = "topLeft";
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Fallback: choose based on available space
|
|
84
|
+
const vertical = spaceBottom >= spaceTop ? "bottom" : "top";
|
|
85
|
+
const horizontal = spaceRight >= spaceLeft ? "Right" : "Left";
|
|
86
|
+
position = `${vertical}${horizontal}`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
let top;
|
|
90
|
+
let left;
|
|
91
|
+
if (position.startsWith("bottom")) {
|
|
92
|
+
top = triggerRect.bottom;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
top = triggerRect.top - popupHeight;
|
|
96
|
+
}
|
|
97
|
+
if (position.endsWith("Right")) {
|
|
98
|
+
left = triggerRect.left;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
left = triggerRect.right - popupWidth;
|
|
102
|
+
}
|
|
103
|
+
return { position, top, left };
|
|
104
|
+
}
|
|
105
|
+
function computeVerticalPosition(triggerRect, _popupWidth, popupHeight, bounds, preferredPosition) {
|
|
106
|
+
const spaceTop = triggerRect.top - bounds.top;
|
|
107
|
+
const spaceBottom = bounds.bottom - triggerRect.bottom;
|
|
108
|
+
let position;
|
|
109
|
+
if (preferredPosition !== "auto") {
|
|
110
|
+
position = preferredPosition;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
position = spaceBottom >= popupHeight || spaceBottom >= spaceTop ? "bottom" : "top";
|
|
114
|
+
}
|
|
115
|
+
const top = position === "bottom" ? triggerRect.bottom : triggerRect.top - popupHeight;
|
|
116
|
+
const left = triggerRect.left;
|
|
117
|
+
return { position, top, left };
|
|
118
|
+
}
|
|
119
|
+
function computeCardinalPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition) {
|
|
120
|
+
const spaceTop = triggerRect.top - bounds.top;
|
|
121
|
+
const spaceBottom = bounds.bottom - triggerRect.bottom;
|
|
122
|
+
const spaceLeft = triggerRect.left - bounds.left;
|
|
123
|
+
const spaceRight = bounds.right - triggerRect.right;
|
|
124
|
+
let position;
|
|
125
|
+
if (preferredPosition !== "auto") {
|
|
126
|
+
position = preferredPosition;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Pick direction with most space
|
|
130
|
+
const spaces = [
|
|
131
|
+
{ dir: "bottom", space: spaceBottom },
|
|
132
|
+
{ dir: "right", space: spaceRight },
|
|
133
|
+
{ dir: "top", space: spaceTop },
|
|
134
|
+
{ dir: "left", space: spaceLeft },
|
|
135
|
+
];
|
|
136
|
+
spaces.sort((a, b) => b.space - a.space);
|
|
137
|
+
position = spaces[0].dir;
|
|
138
|
+
}
|
|
139
|
+
let top;
|
|
140
|
+
let left;
|
|
141
|
+
switch (position) {
|
|
142
|
+
case "top":
|
|
143
|
+
top = triggerRect.top - popupHeight;
|
|
144
|
+
left = triggerRect.left + (triggerRect.width - popupWidth) / 2;
|
|
145
|
+
break;
|
|
146
|
+
case "bottom":
|
|
147
|
+
top = triggerRect.bottom;
|
|
148
|
+
left = triggerRect.left + (triggerRect.width - popupWidth) / 2;
|
|
149
|
+
break;
|
|
150
|
+
case "left":
|
|
151
|
+
top = triggerRect.top + (triggerRect.height - popupHeight) / 2;
|
|
152
|
+
left = triggerRect.left - popupWidth;
|
|
153
|
+
break;
|
|
154
|
+
case "right":
|
|
155
|
+
top = triggerRect.top + (triggerRect.height - popupHeight) / 2;
|
|
156
|
+
left = triggerRect.right;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
return { position, top, left };
|
|
160
|
+
}
|
package/package.json
CHANGED
package/styles/Alert.css
CHANGED
package/styles/Dialog.css
CHANGED
|
@@ -1,32 +1,43 @@
|
|
|
1
1
|
/* ========================================
|
|
2
2
|
Dialog Component
|
|
3
3
|
======================================== */
|
|
4
|
-
.PaDialog-overlay {
|
|
5
|
-
position: fixed;
|
|
6
|
-
inset: 0;
|
|
7
|
-
z-index: var(--z-modal);
|
|
8
|
-
display: flex;
|
|
9
|
-
align-items: center;
|
|
10
|
-
justify-content: center;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.PaDialog-backdrop {
|
|
14
|
-
position: absolute;
|
|
15
|
-
inset: 0;
|
|
16
|
-
background: rgba(0, 0, 0, 0.5);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
4
|
.PaDialog {
|
|
20
|
-
position: relative;
|
|
21
|
-
z-index: 1;
|
|
22
|
-
display: flex;
|
|
23
|
-
flex-direction: column;
|
|
24
|
-
min-width: 320px;
|
|
25
5
|
max-width: 90vw;
|
|
26
|
-
|
|
6
|
+
padding: 0;
|
|
27
7
|
background: var(--color-bg-elevated);
|
|
8
|
+
border: none;
|
|
28
9
|
border-radius: var(--radius-lg);
|
|
29
10
|
box-shadow: var(--shadow-lg);
|
|
11
|
+
|
|
12
|
+
&[open] {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&:modal {
|
|
18
|
+
max-height: 90vh;
|
|
19
|
+
margin: auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&::backdrop {
|
|
23
|
+
background: rgba(0, 0, 0, 0.5);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Size variants */
|
|
27
|
+
&.size-sm {
|
|
28
|
+
width: 320px;
|
|
29
|
+
max-width: 100%;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&.size-md {
|
|
33
|
+
width: 480px;
|
|
34
|
+
max-width: 100%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&.size-lg {
|
|
38
|
+
width: 640px;
|
|
39
|
+
max-width: 100%;
|
|
40
|
+
}
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
.PaDialog-header {
|
package/styles/MultiSelect.css
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
border-color: var(--color-primary);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
&:has(+ .PaMultiSelect-dropdown:popover-open) {
|
|
49
49
|
border-color: var(--color-primary);
|
|
50
50
|
box-shadow: 0 0 0 3px var(--color-primary-light);
|
|
51
51
|
}
|
|
@@ -77,7 +77,11 @@
|
|
|
77
77
|
.PaMultiSelect-chipRemove {
|
|
78
78
|
display: flex;
|
|
79
79
|
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
width: 25px;
|
|
82
|
+
height: 25px;
|
|
80
83
|
padding: 0;
|
|
84
|
+
margin: calc(-1 * var(--space-1));
|
|
81
85
|
color: var(--color-text-muted);
|
|
82
86
|
cursor: pointer;
|
|
83
87
|
background: transparent;
|
|
@@ -96,12 +100,11 @@
|
|
|
96
100
|
|
|
97
101
|
.PaMultiSelect-dropdown {
|
|
98
102
|
position: absolute;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
left: 0;
|
|
102
|
-
z-index: var(--z-dropdown);
|
|
103
|
+
inset: auto;
|
|
104
|
+
z-index: var(--z-popover);
|
|
103
105
|
max-height: 250px;
|
|
104
106
|
padding: var(--space-2);
|
|
107
|
+
margin: 0;
|
|
105
108
|
overflow-y: auto;
|
|
106
109
|
background: var(--color-bg-elevated);
|
|
107
110
|
border: 1px solid var(--color-border);
|
|
@@ -134,24 +137,6 @@
|
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
.PaMultiSelect-checkbox {
|
|
138
|
-
display: flex;
|
|
139
|
-
align-items: center;
|
|
140
|
-
justify-content: center;
|
|
141
|
-
width: 16px;
|
|
142
|
-
height: 16px;
|
|
143
|
-
font-size: var(--text-xs);
|
|
144
|
-
color: var(--color-primary);
|
|
145
|
-
border: 1px solid var(--color-border);
|
|
146
|
-
border-radius: var(--radius-sm);
|
|
147
|
-
|
|
148
|
-
.PaMultiSelect-option.selected & {
|
|
149
|
-
color: var(--color-text-inverse);
|
|
150
|
-
background: var(--color-primary);
|
|
151
|
-
border-color: var(--color-primary);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
140
|
.PaMultiSelect-error {
|
|
156
141
|
font-size: var(--text-xs);
|
|
157
142
|
color: var(--color-danger);
|
package/styles/SplitButton.css
CHANGED
|
@@ -68,21 +68,15 @@
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
.PaSplitBtn-menu {
|
|
71
|
-
|
|
72
|
-
top: calc(100% + var(--space-1));
|
|
73
|
-
right: 0;
|
|
71
|
+
inset: auto;
|
|
74
72
|
z-index: var(--z-dropdown);
|
|
75
|
-
display: none;
|
|
76
73
|
min-width: 180px;
|
|
77
74
|
padding: var(--space-2);
|
|
75
|
+
margin: 0;
|
|
78
76
|
background: var(--color-bg-elevated);
|
|
79
77
|
border: 1px solid var(--color-border);
|
|
80
78
|
border-radius: var(--radius-md);
|
|
81
79
|
box-shadow: var(--shadow-md);
|
|
82
|
-
|
|
83
|
-
.PaSplitBtn.open & {
|
|
84
|
-
display: block;
|
|
85
|
-
}
|
|
86
80
|
}
|
|
87
81
|
|
|
88
82
|
.PaSplitBtn-menuItem {
|
package/styles/Tooltip.css
CHANGED
|
@@ -6,29 +6,15 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
.PaTooltip {
|
|
9
|
-
|
|
10
|
-
z-index: var(--z-
|
|
9
|
+
inset: auto;
|
|
10
|
+
z-index: var(--z-popover);
|
|
11
11
|
max-width: 200px;
|
|
12
12
|
padding: var(--space-2) var(--space-3);
|
|
13
|
+
margin: 0;
|
|
13
14
|
font-size: var(--text-xs);
|
|
14
15
|
color: var(--color-text-inverse);
|
|
15
16
|
pointer-events: none;
|
|
16
17
|
background: var(--color-text);
|
|
18
|
+
border: none;
|
|
17
19
|
border-radius: var(--radius);
|
|
18
|
-
|
|
19
|
-
&.top {
|
|
20
|
-
transform: translate(-50%, -100%);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
&.bottom {
|
|
24
|
-
transform: translateX(-50%);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
&.left {
|
|
28
|
-
transform: translate(-100%, -50%);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
&.right {
|
|
32
|
-
transform: translateY(-50%);
|
|
33
|
-
}
|
|
34
20
|
}
|
package/styles/Tree.css
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.PaTree-node {
|
|
19
|
+
position: relative;
|
|
19
20
|
padding: var(--space-1) 0;
|
|
20
21
|
list-style: none;
|
|
21
22
|
}
|
|
@@ -141,7 +142,7 @@
|
|
|
141
142
|
.PaTree-children .PaTree-node::before {
|
|
142
143
|
position: absolute;
|
|
143
144
|
top: 0;
|
|
144
|
-
left: calc(-
|
|
145
|
+
left: calc(var(--space-2) + 11px - var(--space-6));
|
|
145
146
|
height: 100%;
|
|
146
147
|
content: "";
|
|
147
148
|
border-left: 1.5px solid var(--color-border);
|
|
@@ -150,8 +151,8 @@
|
|
|
150
151
|
.PaTree-children .PaTree-node::after {
|
|
151
152
|
position: absolute;
|
|
152
153
|
top: 1rem;
|
|
153
|
-
left: calc(-
|
|
154
|
-
width: var(--space-
|
|
154
|
+
left: calc(var(--space-2) + 11px - var(--space-6));
|
|
155
|
+
width: calc(var(--space-6));
|
|
155
156
|
content: "";
|
|
156
157
|
border-top: 1.5px solid var(--color-border);
|
|
157
158
|
}
|