@primer/react
Version:
An implementation of GitHub's Primer Design System using React
261 lines (259 loc) • 11.5 kB
JavaScript
import { useMergedRefs } from "../hooks/useMergedRefs.js";
import useIsomorphicLayoutEffect from "../utils/useIsomorphicLayoutEffect.js";
import { useOverlay } from "../hooks/useOverlay.js";
import Portal_default from "../Portal/index.js";
import { useFeatureFlag } from "../FeatureFlags/useFeatureFlag.js";
import Overlay_module_css_default from "./Overlay.module.css.js";
import { c } from "react-compiler-runtime";
import { clsx } from "clsx";
import { jsx } from "react/jsx-runtime";
import React, { useEffect, useRef } from "react";
//#region src/Overlay/Overlay.tsx
const animationDuration = 200;
function getSlideAnimationStartingVector(anchorSide) {
if (anchorSide !== null && anchorSide !== void 0 && anchorSide.endsWith("bottom")) return {
x: 0,
y: -1
};
else if (anchorSide !== null && anchorSide !== void 0 && anchorSide.endsWith("top")) return {
x: 0,
y: 1
};
else if (anchorSide !== null && anchorSide !== void 0 && anchorSide.endsWith("right")) return {
x: -1,
y: 0
};
else if (anchorSide !== null && anchorSide !== void 0 && anchorSide.endsWith("left")) return {
x: 1,
y: 0
};
return {
x: 0,
y: 0
};
}
/**
* An `Overlay` is a flexible floating surface, used to display transient content such as menus,
* selection options, dialogs, and more. Overlays use shadows to express elevation. The `Overlay`
* component handles all behaviors needed by overlay UIs as well as the common styles that all overlays * should have.
* @param height Sets the height of the `Overlay`, pick from our set list of heights, or pass `auto` to automatically set the height based on the content of the `Overlay`, or pass `initial` to set the height based on the initial content of the `Overlay` (i.e. ignoring content changes). `xsmall` corresponds to `192px`, `small` corresponds to `256px`, `medium` corresponds to `320px`, `large` corresponds to `432px`, `xlarge` corresponds to `600px`.
* @param width Sets the width of the `Overlay`, pick from our set list of widths, or pass `auto` to automatically set the width based on the content of the `Overlay`. `small` corresponds to `256px`, `medium` corresponds to `320px`, `large` corresponds to `480px`, `xlarge` corresponds to `640px`, `xxlarge` corresponds to `960px`.
* @param maxHeight Sets the maximum height of the `Overlay`, pick from our set list of heights. `xsmall` corresponds to `192px`, `small` corresponds to `256px`, `medium` corresponds to `320px`, `large` corresponds to `432px`, `xlarge` corresponds to `600px`.
* @param top Optional. Vertical top position of the overlay, relative to its closest positioned ancestor (often its `Portal`).
* @param left Optional. Horizontal left position of the overlay, relative to its closest positioned ancestor (often its `Portal`).
* @param right Optional. Horizontal right position of the overlay, relative to its closest positioned ancestor (often its `Portal`).
* @param bottom Optional. Vertical bottom position of the overlay, relative to its closest positioned ancestor (often its `Portal`).
* @param position Optional. Sets how an element is positioned in a document. Defaults to `absolute` positioning.
*/
const BaseOverlay = /*#__PURE__*/ React.forwardRef(({ visibility, height, width, top, left, right, bottom, position, style: styleFromProps, className, maxHeight, maxWidth, as: Component = "div", ...rest }, forwardedRef) => {
return /*#__PURE__*/ jsx(Component, {
...rest,
ref: forwardedRef,
style: {
"--top": typeof top === "number" ? `${top}px` : top,
"--left": typeof left === "number" ? `${left}px` : left,
"--right": typeof right === "number" ? `${right}px` : right,
"--bottom": typeof bottom === "number" ? `${bottom}px` : bottom,
position,
...styleFromProps
},
[`data-width-${width}`]: "",
[`data-max-width-${maxWidth}`]: maxWidth ? "" : void 0,
[`data-height-${height}`]: "",
[`data-max-height-${maxHeight}`]: maxHeight ? "" : void 0,
[`data-visibility-${visibility}`]: "",
[`data-overflow-${rest.overflow}`]: rest.overflow ? "" : void 0,
className: clsx(className, Overlay_module_css_default.Overlay)
});
});
/**
* @param anchorSide If provided, the Overlay will slide into position from the side of the anchor with a brief animation
* @param height Sets the height of the `Overlay`, pick from our set list of heights, or pass `auto` to automatically set the height based on the content of the `Overlay`, or pass `initial` to set the height based on the initial content of the `Overlay` (i.e. ignoring content changes). `xsmall` corresponds to `192px`, `small` corresponds to `256px`, `medium` corresponds to `320px`, `large` corresponds to `432px`, `xlarge` corresponds to `600px`.
* @param ignoreClickRefs Optional. An array of ref objects to ignore clicks on in the `onOutsideClick` behavior. This is often used to ignore clicking on the element that toggles the open/closed state for the `Overlay` to prevent the `Overlay` from being toggled twice.
* @param initialFocusRef Optional. Ref for the element to focus when the `Overlay` is opened. If nothing is provided, the first focusable element in the `Overlay` body is focused.
* @param left Optional. Horizontal left position of the overlay, relative to its closest positioned ancestor (often its `Portal`).
* @param onClickOutside Required. Function to call when clicking outside of the `Overlay`. Typically this function removes the Overlay.
* @param onEscape Required. Function to call when user presses `Escape`. Typically this function removes the Overlay.
* @param portalContainerName Optional. The name of the portal container to render the Overlay into.
* @param preventOverflow Optional. The Overlay width will be adjusted responsively if there is not enough space to display the Overlay. If `preventOverflow` is `true`, the width of the `Overlay` will not be adjusted.
* @param preventFocusOnOpen Optional. If 'true', focus will not be applied when the component is first mounted, even if initialFocusRef prop is given.
* @param returnFocusRef Required. Ref for the element to focus when the `Overlay` is closed.
* @param right Optional. Horizontal right position of the overlay, relative to its closest positioned ancestor (often its `Portal`).
* @param width Sets the width of the `Overlay`, pick from our set list of widths, or pass `auto` to automatically set the width based on the content of the `Overlay`. `small` corresponds to `256px`, `medium` corresponds to `320px`, `large` corresponds to `480px`, `xlarge` corresponds to `640px`, `xxlarge` corresponds to `960px`.
*/
const Overlay = /*#__PURE__*/ React.forwardRef((t0, forwardedRef) => {
const $ = c(47);
let _PrivateDisablePortal;
let anchorSide;
let ignoreClickRefs;
let initialFocusRef;
let left;
let onClickOutside;
let onEscape;
let portalContainerName;
let preventFocusOnOpen;
let props;
let responsiveVariant;
let returnFocusRef;
let right;
let t1;
let t2;
let t3;
let t4;
let t5;
if ($[0] !== t0) {
({anchorSide, _PrivateDisablePortal, height: t1, ignoreClickRefs, initialFocusRef, left, onClickOutside, onEscape, portalContainerName, preventOverflow: t2, preventFocusOnOpen, returnFocusRef, right, role: t3, visibility: t4, width: t5, responsiveVariant, ...props} = t0);
$[0] = t0;
$[1] = _PrivateDisablePortal;
$[2] = anchorSide;
$[3] = ignoreClickRefs;
$[4] = initialFocusRef;
$[5] = left;
$[6] = onClickOutside;
$[7] = onEscape;
$[8] = portalContainerName;
$[9] = preventFocusOnOpen;
$[10] = props;
$[11] = responsiveVariant;
$[12] = returnFocusRef;
$[13] = right;
$[14] = t1;
$[15] = t2;
$[16] = t3;
$[17] = t4;
$[18] = t5;
} else {
_PrivateDisablePortal = $[1];
anchorSide = $[2];
ignoreClickRefs = $[3];
initialFocusRef = $[4];
left = $[5];
onClickOutside = $[6];
onEscape = $[7];
portalContainerName = $[8];
preventFocusOnOpen = $[9];
props = $[10];
responsiveVariant = $[11];
returnFocusRef = $[12];
right = $[13];
t1 = $[14];
t2 = $[15];
t3 = $[16];
t4 = $[17];
t5 = $[18];
}
const height = t1 === void 0 ? "auto" : t1;
const preventOverflow = t2 === void 0 ? true : t2;
const role = t3 === void 0 ? "none" : t3;
const visibility = t4 === void 0 ? "visible" : t4;
const width = t5 === void 0 ? "auto" : t5;
const overlayRef = useRef(null);
const mergedOverlayRef = useMergedRefs(forwardedRef, overlayRef);
const cssAnchorPositioning = useFeatureFlag("primer_react_css_anchor_positioning");
let t6;
if ($[19] !== ignoreClickRefs || $[20] !== initialFocusRef || $[21] !== onClickOutside || $[22] !== onEscape || $[23] !== preventFocusOnOpen || $[24] !== returnFocusRef) {
t6 = {
overlayRef,
returnFocusRef,
onEscape,
ignoreClickRefs,
onClickOutside,
initialFocusRef,
preventFocusOnOpen
};
$[19] = ignoreClickRefs;
$[20] = initialFocusRef;
$[21] = onClickOutside;
$[22] = onEscape;
$[23] = preventFocusOnOpen;
$[24] = returnFocusRef;
$[25] = t6;
} else t6 = $[25];
useOverlay(t6);
let t7;
let t8;
if ($[26] !== height) {
t7 = () => {
var _overlayRef$current;
if (height === "initial" && (_overlayRef$current = overlayRef.current) !== null && _overlayRef$current !== void 0 && _overlayRef$current.clientHeight) overlayRef.current.style.height = `${overlayRef.current.clientHeight}px`;
};
t8 = [height];
$[26] = height;
$[27] = t7;
$[28] = t8;
} else {
t7 = $[27];
t8 = $[28];
}
useEffect(t7, t8);
let t10;
let t9;
if ($[29] !== anchorSide || $[30] !== visibility) {
t9 = () => {
var _overlayRef$current2;
const { x, y } = getSlideAnimationStartingVector(anchorSide);
if (!x && !y || !((_overlayRef$current2 = overlayRef.current) !== null && _overlayRef$current2 !== void 0 && _overlayRef$current2.animate) || visibility === "hidden") return;
overlayRef.current.animate({ transform: [`translate(${8 * x}px, ${8 * y}px)`, "translate(0, 0)"] }, {
duration: animationDuration,
easing: "cubic-bezier(0.33, 1, 0.68, 1)"
});
};
t10 = [
anchorSide,
8,
"cubic-bezier(0.33, 1, 0.68, 1)",
visibility
];
$[29] = anchorSide;
$[30] = visibility;
$[31] = t10;
$[32] = t9;
} else {
t10 = $[31];
t9 = $[32];
}
useIsomorphicLayoutEffect(t9, t10);
const leftPosition = left === void 0 && right === void 0 ? 0 : left;
const t11 = !preventOverflow ? true : void 0;
let t12;
if ($[33] !== height || $[34] !== leftPosition || $[35] !== mergedOverlayRef || $[36] !== props || $[37] !== responsiveVariant || $[38] !== right || $[39] !== role || $[40] !== t11 || $[41] !== visibility || $[42] !== width) {
t12 = /*#__PURE__*/ jsx(BaseOverlay, {
role,
width,
"data-reflow-container": t11,
ref: mergedOverlayRef,
left: leftPosition,
right,
height,
visibility,
"data-responsive": responsiveVariant,
...props
});
$[33] = height;
$[34] = leftPosition;
$[35] = mergedOverlayRef;
$[36] = props;
$[37] = responsiveVariant;
$[38] = right;
$[39] = role;
$[40] = t11;
$[41] = visibility;
$[42] = width;
$[43] = t12;
} else t12 = $[43];
const overlayContent = t12;
if (_PrivateDisablePortal && cssAnchorPositioning) return overlayContent;
let t13;
if ($[44] !== overlayContent || $[45] !== portalContainerName) {
t13 = /*#__PURE__*/ jsx(Portal_default, {
containerName: portalContainerName,
children: overlayContent
});
$[44] = overlayContent;
$[45] = portalContainerName;
$[46] = t13;
} else t13 = $[46];
return t13;
});
//#endregion
export { BaseOverlay, Overlay as default };