@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
222 lines • 14.7 kB
JavaScript
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Drawer = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
/**
*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const react_1 = require("@floating-ui/react");
const classnames_1 = __importDefault(require("classnames"));
const re_resizable_1 = require("re-resizable");
const react_2 = require("react");
const utils_1 = require("../_common/utils");
const clean_icon_button_1 = require("../clean-icon-button");
const conditional_wrap_1 = require("../conditional-wrap");
const XMarkIconOutline_1 = require("../icons/generated/heroIcons/XMarkIconOutline");
const typography_1 = require("../typography");
const KEYBOARD_RESIZE_STEP_PX = 16;
function parsePixelSize(value) {
if (value === undefined) {
return undefined;
}
if (typeof value === 'number') {
return value;
}
if (/^\d+(\.\d+)?px$/i.test(value)) {
return parseFloat(value);
}
return undefined;
}
function parsePercentage(value, referenceWidth) {
if (typeof value !== 'string' ||
!value.endsWith('%') ||
referenceWidth === undefined) {
return undefined;
}
const percent = parseFloat(value);
return Number.isNaN(percent) ? undefined : (percent / 100) * referenceWidth;
}
function DrawerResizeHandle({ handleSide, onResizeBy, valueMax, valueMin, valueNow, }) {
const handleKeyDown = (0, react_2.useCallback)((e) => {
const isArrowKey = e.key === 'ArrowLeft' || e.key === 'ArrowRight';
if (isArrowKey) {
e.preventDefault();
const delta = handleSide === 'right'
? e.key === 'ArrowRight'
? KEYBOARD_RESIZE_STEP_PX
: -KEYBOARD_RESIZE_STEP_PX
: e.key === 'ArrowLeft'
? KEYBOARD_RESIZE_STEP_PX
: -KEYBOARD_RESIZE_STEP_PX;
onResizeBy(delta);
}
}, [handleSide, onResizeBy]);
return (
/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- Resize handle is focusable for keyboard resize. */
(0, jsx_runtime_1.jsx)("div", { "aria-label": `Resize drawer with arrow keys. Handle on ${handleSide}.`, "aria-orientation": "vertical", "aria-valuemax": valueMax, "aria-valuemin": valueMin, "aria-valuenow": valueNow !== null && valueNow !== void 0 ? valueNow : 0, "aria-valuetext": `drawer width ${valueNow !== null && valueNow !== void 0 ? valueNow : 0}px`, className: "ndl-drawer-resize-handle", "data-drawer-handle": handleSide, onKeyDown: handleKeyDown, role: "separator", style: { height: '100%', width: '100%' },
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- Resize handle is focusable for keyboard resize.
tabIndex: 0 }));
}
const DrawerComponent = function DrawerComponent(_a) {
var _b;
var { children, className = '', isExpanded, onExpandedChange, position = 'left', type = 'overlay', isResizeable = false, resizeableProps, isCloseable = true, isPortaled = false, portalProps = {}, closeOnEscape = type === 'modal' ? true : false, closeOnClickOutside = false, ariaLabel, htmlAttributes, style, ref, as } = _a, restProps = __rest(_a, ["children", "className", "isExpanded", "onExpandedChange", "position", "type", "isResizeable", "resizeableProps", "isCloseable", "isPortaled", "portalProps", "closeOnEscape", "closeOnClickOutside", "ariaLabel", "htmlAttributes", "style", "ref", "as"]);
const resizableRef = (0, react_2.useRef)(null);
const [resizeWidth, setResizeWidth] = (0, react_2.useState)(0);
if ((type === 'modal' || type === 'overlay') && !ariaLabel) {
(0, utils_1.needleWarningMessage)('A Drawer should have an aria-label when type is "modal" or "overlay" to be accessible.');
}
const { refs, context } = (0, react_1.useFloating)({
onOpenChange: onExpandedChange,
open: isExpanded,
});
/** Dismiss (escape + click outside) applies to portaled overlay and to non-portaled overlay. */
const shouldUseDismiss = type === 'modal' ||
(type === 'overlay' && !isPortaled && isExpanded) ||
(type === 'overlay' && isPortaled);
const dismiss = (0, react_1.useDismiss)(context, {
enabled: shouldUseDismiss,
escapeKey: closeOnEscape && type !== 'push',
outsidePress: closeOnClickOutside && type !== 'push',
});
const role = (0, react_1.useRole)(context, {
enabled: type === 'modal' || type === 'overlay',
role: 'dialog',
});
const { getFloatingProps } = (0, react_1.useInteractions)([dismiss, role]);
const resizableMergedRef = (0, react_1.useMergeRefs)([resizableRef, ref]);
const handleResizeBy = (0, react_2.useCallback)((delta) => {
var _a, _b, _c, _d;
if (!resizableRef.current) {
return;
}
const current = resizableRef.current.size;
const parentWidth = (_b = (_a = resizableRef.current.resizable) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.offsetWidth;
const min = (_c = parsePixelSize(resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.minWidth)) !== null && _c !== void 0 ? _c : parsePercentage(resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.minWidth, parentWidth);
const max = (_d = parsePixelSize(resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.maxWidth)) !== null && _d !== void 0 ? _d : parsePercentage(resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.maxWidth, parentWidth);
const newWidth = Math.max(min !== null && min !== void 0 ? min : 0, Math.min(max !== null && max !== void 0 ? max : Number.POSITIVE_INFINITY, current.width + delta));
resizableRef.current.updateSize({
height: '100%',
width: newWidth,
});
setResizeWidth(newWidth);
}, [resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.minWidth, resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.maxWidth]);
const handleResize = (0, react_2.useCallback)((event, direction, elementRef, delta) => {
var _a;
setResizeWidth(elementRef.offsetWidth);
(_a = resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.onResize) === null || _a === void 0 ? void 0 : _a.call(resizeableProps, event, direction, elementRef, delta);
}, [resizeableProps]);
(0, react_2.useEffect)(() => {
if (!isResizeable || !resizableRef.current) {
return;
}
const width = resizableRef.current.size.width;
if (width > 0) {
setResizeWidth(width);
}
}, [isResizeable]);
const classes = (0, classnames_1.default)('ndl-drawer', className, {
'ndl-drawer-expanded': isExpanded,
'ndl-drawer-left': position === 'left',
'ndl-drawer-modal': type === 'modal',
'ndl-drawer-overlay': type === 'overlay',
'ndl-drawer-portaled': isPortaled && type === 'overlay',
'ndl-drawer-push': type === 'push',
'ndl-drawer-right': position === 'right',
});
const resizableStylePosition = type === 'overlay' ? 'absolute' : 'relative';
const Component = as !== null && as !== void 0 ? as : 'div';
const handleClose = (0, react_2.useCallback)(() => {
onExpandedChange === null || onExpandedChange === void 0 ? void 0 : onExpandedChange(false);
}, [onExpandedChange]);
const closeButton = (0, react_2.useMemo)(() => isCloseable || type === 'modal' ? ((0, jsx_runtime_1.jsx)(clean_icon_button_1.CleanIconButton, { className: "ndl-drawer-close-button", onClick: handleClose, description: null, size: "medium", htmlAttributes: {
'aria-label': 'Close',
}, children: (0, jsx_runtime_1.jsx)(XMarkIconOutline_1.XMarkIconOutline, {}) })) : null, [handleClose, isCloseable, type]);
const resizableContent = ((0, jsx_runtime_1.jsxs)(re_resizable_1.Resizable, Object.assign({ as: Component, defaultSize: {
height: '100%',
width: 'auto',
} }, resizeableProps, { className: classes, style: Object.assign(Object.assign({ position: resizableStylePosition }, style), resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.style), boundsByDirection: true, bounds: (_b = resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.bounds) !== null && _b !== void 0 ? _b : 'parent', handleStyles: Object.assign(Object.assign({}, (position === 'left' && { right: { right: '-8px' } })), (position === 'right' && { left: { left: '-8px' } })), enable: {
bottom: false,
bottomLeft: false,
bottomRight: false,
left: position === 'right',
right: position === 'left',
top: false,
topLeft: false,
topRight: false,
}, handleComponent: position === 'left'
? {
right: ((0, jsx_runtime_1.jsx)(DrawerResizeHandle, { handleSide: "right", onResizeBy: handleResizeBy, valueMax: parsePixelSize(resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.maxWidth), valueMin: parsePixelSize(resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.minWidth), valueNow: resizeWidth })),
}
: {
left: ((0, jsx_runtime_1.jsx)(DrawerResizeHandle, { handleSide: "left", onResizeBy: handleResizeBy, valueMax: parsePixelSize(resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.maxWidth), valueMin: parsePixelSize(resizeableProps === null || resizeableProps === void 0 ? void 0 : resizeableProps.minWidth), valueNow: resizeWidth })),
}, onResize: handleResize, ref: resizableMergedRef }, restProps, htmlAttributes, { children: [closeButton, children] })));
const regularContent = ((0, jsx_runtime_1.jsxs)(Component, Object.assign({ className: (0, classnames_1.default)(classes), style: style, ref: ref }, restProps, htmlAttributes, { children: [closeButton, children] })));
/** what content to render*/
const content = isResizeable ? resizableContent : regularContent;
// const floatingRefs = useMergeRefs([refs.setFloating, refs.setReference]);
/** modal behavior */
if (type === 'modal' && isExpanded) {
return ((0, jsx_runtime_1.jsxs)(react_1.FloatingPortal, Object.assign({}, portalProps, { children: [(0, jsx_runtime_1.jsx)(react_1.FloatingOverlay, { className: "ndl-drawer-overlay-root", lockScroll: true }), (0, jsx_runtime_1.jsx)(react_1.FloatingFocusManager, { context: context, modal: true, returnFocus: true, children: (0, jsx_runtime_1.jsx)("div", Object.assign({ ref: refs.setFloating }, getFloatingProps(), { "aria-label": ariaLabel, "aria-modal": "true", children: content })) })] })));
}
if (type === 'overlay' && isExpanded) {
return ((0, jsx_runtime_1.jsx)(conditional_wrap_1.ConditionalWrap, { shouldWrap: isPortaled, wrap: (wrapChildren) => ((0, jsx_runtime_1.jsx)(react_1.FloatingPortal, Object.assign({ preserveTabOrder: true }, portalProps, { children: wrapChildren }))), children: (0, jsx_runtime_1.jsx)(react_1.FloatingFocusManager, { disabled: !isExpanded, context: context, modal: true, returnFocus: true, children: (0, jsx_runtime_1.jsx)("div", Object.assign({ ref: refs.setFloating }, getFloatingProps(), { "aria-label": ariaLabel, "aria-modal": "true", children: content })) }) }));
}
return content;
};
DrawerComponent.displayName = 'Drawer';
const DrawerHeader = (_a) => {
var { children, className = '', htmlAttributes } = _a, restProps = __rest(_a, ["children", "className", "htmlAttributes"]);
const classes = (0, classnames_1.default)('ndl-drawer-header', className);
if (typeof children === 'string' || typeof children === 'number') {
return ((0, jsx_runtime_1.jsx)(typography_1.Typography, Object.assign({ className: classes, variant: "title-3" }, restProps, htmlAttributes, { children: children })));
}
return ((0, jsx_runtime_1.jsx)("div", Object.assign({ className: classes }, restProps, htmlAttributes, { children: children })));
};
const DrawerActions = (_a) => {
var { children, className = '', htmlAttributes } = _a, restProps = __rest(_a, ["children", "className", "htmlAttributes"]);
const classes = (0, classnames_1.default)('ndl-drawer-actions', className);
return ((0, jsx_runtime_1.jsx)("div", Object.assign({ className: classes }, restProps, htmlAttributes, { children: children })));
};
const DrawerBody = (_a) => {
var { children, className = '', htmlAttributes } = _a, restProps = __rest(_a, ["children", "className", "htmlAttributes"]);
const classes = (0, classnames_1.default)('ndl-drawer-body', className);
return ((0, jsx_runtime_1.jsx)("div", { className: "ndl-drawer-body-wrapper", children: (0, jsx_runtime_1.jsx)("div", Object.assign({ className: classes }, restProps, htmlAttributes, { children: children })) }));
};
const Drawer = Object.assign(DrawerComponent, {
Actions: DrawerActions,
Body: DrawerBody,
Header: DrawerHeader,
});
exports.Drawer = Drawer;
//# sourceMappingURL=Drawer.js.map