@adamjanicki/ui 1.6.3 → 1.6.5

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.
@@ -25,13 +25,19 @@ type Drawer = {
25
25
  * Content hidden within this accordion drawer
26
26
  */
27
27
  content: React.ReactNode;
28
- };
29
- type DrawerProps = {
28
+ /**
29
+ * Whether the drawer is open
30
+ */
30
31
  open: boolean;
32
+ /**
33
+ * Callback that fires when the open state changes for this drawer
34
+ */
31
35
  onOpenChange: (open: boolean) => void;
36
+ };
37
+ type DrawerProps = {
32
38
  item: Drawer;
33
39
  duration?: number;
34
40
  showDivider: boolean;
35
41
  };
36
- declare const Drawer: ({ item, open, onOpenChange, duration, showDivider, }: DrawerProps) => import("react/jsx-runtime").JSX.Element;
42
+ declare const Drawer: ({ item, duration, showDivider }: DrawerProps) => import("react/jsx-runtime").JSX.Element;
37
43
  export default Accordion;
@@ -21,7 +21,7 @@ var __rest = (this && this.__rest) || function (s, e) {
21
21
  return t;
22
22
  };
23
23
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
24
- import React, { useState, useRef, useEffect } from "react";
24
+ import React, { useRef, useState, useEffect } from "react";
25
25
  import Box from "../Box/Box";
26
26
  import Icon from "../Icon";
27
27
  import { UnstyledButton } from "../Button";
@@ -29,25 +29,13 @@ import Animated from "../Animated";
29
29
  import { classNames } from "../../functions";
30
30
  var Accordion = React.forwardRef(function (_a, ref) {
31
31
  var drawers = _a.drawers, className = _a.className, duration = _a.duration, hideDividers = _a.hideDividers, layout = _a.layout, rest = __rest(_a, ["drawers", "className", "duration", "hideDividers", "layout"]);
32
- var _b = useState(new Set()), openIndices = _b[0], setOpenIndices = _b[1];
33
- return (_jsx(Box, __assign({ layout: __assign({ axis: "y" }, layout) }, rest, { className: classNames("aui-accordion aui-corners--rounded", className), ref: ref, children: drawers.map(function (item, i) { return (_jsx(Drawer, { item: item, open: openIndices.has(i), onOpenChange: function (open) {
34
- return setOpenIndices(function (prev) {
35
- var next = new Set(prev);
36
- if (open) {
37
- next.add(i);
38
- }
39
- else {
40
- next.delete(i);
41
- }
42
- return next;
43
- });
44
- }, duration: duration, showDivider: !hideDividers && i < drawers.length - 1 }, i)); }) })));
32
+ return (_jsx(Box, __assign({ layout: __assign({ axis: "y" }, layout) }, rest, { className: classNames("aui-accordion aui-corners--rounded", className), ref: ref, children: drawers.map(function (item, i) { return (_jsx(Drawer, { item: item, duration: duration, showDivider: !hideDividers && i < drawers.length - 1 }, i)); }) })));
45
33
  });
46
34
  var Drawer = function (_a) {
47
- var item = _a.item, open = _a.open, onOpenChange = _a.onOpenChange, duration = _a.duration, showDivider = _a.showDivider;
35
+ var item = _a.item, duration = _a.duration, showDivider = _a.showDivider;
48
36
  var boxRef = useRef(null);
49
37
  var _b = useState(), height = _b[0], setHeight = _b[1];
50
- var children = item.content;
38
+ var children = item.content, open = item.open, onOpenChange = item.onOpenChange;
51
39
  useEffect(function () {
52
40
  if (open && children && boxRef.current) {
53
41
  setHeight(boxRef.current.offsetHeight);
@@ -55,7 +43,7 @@ var Drawer = function (_a) {
55
43
  }, [open, children]);
56
44
  // TODO: change this to use calc-size when supported
57
45
  // https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size#browser_compatibility
58
- return (_jsxs(_Fragment, { children: [_jsxs(Box, { layout: { axis: "y" }, children: [_jsx(UnstyledButton, { onClick: function () { return onOpenChange(!open); }, children: _jsxs(Box, { layout: { axis: "x", align: "center", gap: "s", padding: "m" }, children: [_jsx(Icon, { size: "xs", icon: open ? "chevron-down" : "chevron-right", className: "aui-accordion-arrow" }), _jsx("span", { className: "aui-accordion-label", children: item.label })] }) }), _jsx(Animated, { style: { overflow: "hidden" }, keepMounted: true, duration: duration, animated: open, animateFrom: {
46
+ return (_jsxs(_Fragment, { children: [_jsxs(Box, { layout: { axis: "y" }, children: [_jsx(UnstyledButton, { onClick: function () { return onOpenChange(!open); }, children: _jsxs(Box, { layout: { axis: "x", align: "center", gap: "s", padding: "m" }, children: [_jsx(Icon, { size: "xs", icon: open ? "chevron-down" : "chevron-right", className: "aui-accordion-arrow" }), _jsx("span", { className: "aui-accordion-label", children: item.label })] }) }), _jsx(Animated, { style: { overflow: "hidden" }, keepMounted: true, duration: duration, visible: open, animateFrom: {
59
47
  style: {
60
48
  visibility: "hidden",
61
49
  height: 0,
@@ -3,15 +3,24 @@ import type { Style } from "../../utils/types";
3
3
  import { type BoxProps } from "../Box/Box";
4
4
  type Props = BoxProps & {
5
5
  /**
6
- * Whether to begin the animation.
6
+ * Whether to begin the animation and render the component.
7
7
  * Set to true to start animation, false to start the exit animation.
8
8
  */
9
- animated: boolean;
9
+ visible: boolean;
10
10
  /**
11
11
  * Duration of the animation in seconds
12
12
  * @default 0.25
13
13
  */
14
- duration?: number;
14
+ duration?: number | {
15
+ /**
16
+ * Length of the forward direction
17
+ */
18
+ forward: number;
19
+ /**
20
+ * Length of the reverse direction
21
+ */
22
+ reverse: number;
23
+ };
15
24
  /**
16
25
  * Whether to keep the component mounted when it is not animated
17
26
  * @default false
@@ -40,6 +49,11 @@ type Props = BoxProps & {
40
49
  */
41
50
  style?: Style;
42
51
  };
52
+ /**
53
+ * The properties to apply a transition
54
+ * @default ['all']
55
+ */
56
+ transitionProperties?: string[];
43
57
  };
44
58
  declare const Animated: React.ForwardRefExoticComponent<Omit<Props, "ref"> & React.RefAttributes<HTMLDivElement>>;
45
59
  export default Animated;
@@ -25,9 +25,12 @@ import React, { useState, useEffect, useRef } from "react";
25
25
  import classNames from "../../functions/classNames";
26
26
  import Box from "../Box/Box";
27
27
  var Animated = React.forwardRef(function (props, ref) {
28
- var animated = props.animated, _a = props.duration, duration = _a === void 0 ? 0.25 : _a, _b = props.keepMounted, keepMounted = _b === void 0 ? false : _b, animateTo = props.animateTo, animateFrom = props.animateFrom, className = props.className, style = props.style, rest = __rest(props, ["animated", "duration", "keepMounted", "animateTo", "animateFrom", "className", "style"]);
29
- var _c = useState(animated || keepMounted), shouldRender = _c[0], setShouldRender = _c[1];
30
- var _d = useState(false), isAnimatingForward = _d[0], setIsAnimatingForward = _d[1];
28
+ var visible = props.visible, _a = props.duration, duration = _a === void 0 ? 0.25 : _a, _b = props.keepMounted, keepMounted = _b === void 0 ? false : _b, _c = props.transitionProperties, transitionProperties = _c === void 0 ? ["all"] : _c, animateTo = props.animateTo, animateFrom = props.animateFrom, className = props.className, style = props.style, rest = __rest(props, ["visible", "duration", "keepMounted", "transitionProperties", "animateTo", "animateFrom", "className", "style"]);
29
+ var forwardDuration = typeof duration === "number" ? duration : duration.forward;
30
+ var reverseDuration = typeof duration === "number" ? duration : duration.reverse;
31
+ var instantForward = forwardDuration <= 0;
32
+ var instantReverse = reverseDuration <= 0;
33
+ var _d = useState("from"), phase = _d[0], setPhase = _d[1];
31
34
  var timeoutRef = useRef(null);
32
35
  var animationFrameRef = useRef(null);
33
36
  var clearRefs = function () {
@@ -41,35 +44,51 @@ var Animated = React.forwardRef(function (props, ref) {
41
44
  }
42
45
  };
43
46
  useEffect(function () {
44
- // initiate forward animation
45
- if (animated && shouldRender) {
46
- clearRefs();
47
- animationFrameRef.current = requestAnimationFrame(function () {
48
- return setIsAnimatingForward(true);
49
- });
50
- }
51
- return clearRefs;
52
- }, [animated, shouldRender]);
53
- useEffect(function () {
54
- // make container element appear on DOM
55
- if (animated) {
56
- setShouldRender(true);
57
- }
58
- // initiate reverse animation
59
- else {
60
- clearRefs();
61
- setIsAnimatingForward(false);
62
- timeoutRef.current = window.setTimeout(function () {
63
- if (!keepMounted) {
64
- setShouldRender(false);
47
+ clearRefs();
48
+ if (visible) {
49
+ if (phase !== "forward") {
50
+ if (instantForward) {
51
+ setPhase("forward");
65
52
  }
66
- }, duration * 1000);
53
+ else {
54
+ animationFrameRef.current = requestAnimationFrame(function () {
55
+ return setPhase("forward");
56
+ });
57
+ }
58
+ }
59
+ }
60
+ else if (phase !== "from") {
61
+ if (instantReverse) {
62
+ setPhase("from");
63
+ }
64
+ else if (phase !== "reverse") {
65
+ setPhase("reverse");
66
+ }
67
+ else if (phase === "reverse") {
68
+ timeoutRef.current = window.setTimeout(function () { return setPhase("from"); }, reverseDuration * 1000);
69
+ }
67
70
  }
68
71
  return clearRefs;
69
- }, [animated, duration, keepMounted]);
70
- if (!shouldRender)
72
+ }, [visible, phase, instantForward, instantReverse, reverseDuration]);
73
+ if (phase === "from" && !keepMounted && !visible)
71
74
  return null;
72
- var currentAnimation = isAnimatingForward ? animateTo : animateFrom;
73
- return (_jsx(Box, __assign({ className: classNames(className, currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.className), style: __assign(__assign({ transition: "all ".concat(duration, "s ease-in-out") }, style), currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.style) }, rest, { ref: ref })));
75
+ var currentAnimation = phase === "forward" || (visible && instantForward)
76
+ ? animateTo
77
+ : animateFrom;
78
+ var transition = undefined;
79
+ if (phase === "forward" && !instantForward) {
80
+ transition = makeTransition(transitionProperties, forwardDuration);
81
+ }
82
+ else if (phase === "reverse" && !instantReverse) {
83
+ transition = makeTransition(transitionProperties, reverseDuration);
84
+ }
85
+ return (_jsx(Box, __assign({ className: classNames(className, currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.className), style: __assign(__assign({ transition: transition }, style), currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.style) }, rest, { ref: ref })));
74
86
  });
87
+ var makeTransition = function (transitionProperties, duration) {
88
+ return transitionProperties.length > 0
89
+ ? transitionProperties
90
+ .map(function (prop) { return "".concat(prop, " ").concat(duration, "s ease-in-out"); })
91
+ .join(", ")
92
+ : undefined;
93
+ };
75
94
  export default Animated;
@@ -1,4 +1,9 @@
1
1
  import React from "react";
2
+ declare const mouseEvents: {
3
+ readonly click: "onClick";
4
+ readonly mousedown: "onMouseDown";
5
+ readonly mouseup: "onMouseUp";
6
+ };
2
7
  type Props = {
3
8
  /**
4
9
  * The children to render.
@@ -11,6 +16,11 @@ type Props = {
11
16
  * @param event - The mouse event object
12
17
  */
13
18
  onClickOutside: (event: MouseEvent) => void;
19
+ /**
20
+ * The mouse event to trigger on
21
+ * @default "click"
22
+ */
23
+ mouseEvent?: keyof typeof mouseEvents;
14
24
  };
15
- declare const ClickOutside: ({ children, onClickOutside, }: Props) => React.JSX.Element;
25
+ declare const ClickOutside: ({ children, onClickOutside, mouseEvent, }: Props) => React.JSX.Element;
16
26
  export default ClickOutside;
@@ -1,6 +1,13 @@
1
1
  import { cloneElement, useCallback, useEffect, useRef } from "react";
2
+ import useMergeRefs from "../../hooks/useMergeRefs";
3
+ var mouseEvents = {
4
+ click: "onClick",
5
+ mousedown: "onMouseDown",
6
+ mouseup: "onMouseUp",
7
+ };
2
8
  var ClickOutside = function (_a) {
3
- var children = _a.children, onClickOutside = _a.onClickOutside;
9
+ var _b;
10
+ var children = _a.children, onClickOutside = _a.onClickOutside, _c = _a.mouseEvent, mouseEvent = _c === void 0 ? "click" : _c;
4
11
  var ref = useRef(null);
5
12
  var clickWithinChildRef = useRef(false);
6
13
  var startedRef = useRef(false);
@@ -14,27 +21,35 @@ var ClickOutside = function (_a) {
14
21
  };
15
22
  }, []);
16
23
  var handleClickOutside = useCallback(function (event) {
24
+ var _a;
17
25
  var clickedWithinChild = clickWithinChildRef.current;
18
26
  clickWithinChildRef.current = false;
19
- if (!startedRef.current || !ref.current || clickedWithinChild)
27
+ var childElement = ref.current;
28
+ if (!startedRef.current || !childElement || clickedWithinChild)
20
29
  return;
21
- if (!event.composedPath().includes(ref.current)) {
30
+ var path = ((_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)) || [];
31
+ var isInside = path.includes(childElement) ||
32
+ childElement.contains(event.target);
33
+ if (!isInside) {
22
34
  onClickOutside(event);
23
35
  }
24
36
  }, [onClickOutside]);
25
37
  useEffect(function () {
26
- document.addEventListener("click", handleClickOutside);
27
- return function () { return document.removeEventListener("click", handleClickOutside); };
28
- }, [handleClickOutside]);
29
- return cloneElement(children, {
30
- ref: ref,
31
- onClick: function (event) {
38
+ document.addEventListener(mouseEvent, handleClickOutside);
39
+ return function () { return document.removeEventListener(mouseEvent, handleClickOutside); };
40
+ }, [handleClickOutside, mouseEvent]);
41
+ var mergedRef = useMergeRefs(ref, children.props.ref);
42
+ var mouseEventPropName = mouseEvents[mouseEvent];
43
+ return cloneElement(children, (_b = {
44
+ ref: mergedRef
45
+ },
46
+ _b[mouseEventPropName] = function (event) {
32
47
  var _a, _b;
33
48
  // point of this is to let us know that click originated
34
49
  // from the child element, so we can ignore it
35
50
  clickWithinChildRef.current = true;
36
- (_b = (_a = children.props) === null || _a === void 0 ? void 0 : _a.onClick) === null || _b === void 0 ? void 0 : _b.call(_a, event);
51
+ (_b = (_a = children.props) === null || _a === void 0 ? void 0 : _a[mouseEventPropName]) === null || _b === void 0 ? void 0 : _b.call(_a, event);
37
52
  },
38
- });
53
+ _b));
39
54
  };
40
55
  export default ClickOutside;
@@ -25,6 +25,7 @@ import React, { useEffect } from "react";
25
25
  import { useFocusTrap, useScrollLock } from "../../hooks";
26
26
  import classNames from "../../functions/classNames";
27
27
  import Box from "../Box/Box";
28
+ import useMergeRefs from "../../hooks/useMergeRefs";
28
29
  var Layer = React.forwardRef(function (_a, ref) {
29
30
  var returnFocusOnEscape = _a.returnFocusOnEscape, disableScrollLock = _a.disableScrollLock, onClose = _a.onClose, children = _a.children, className = _a.className, onMouseDown = _a.onMouseDown, layout = _a.layout, rest = __rest(_a, ["returnFocusOnEscape", "disableScrollLock", "onClose", "children", "className", "onMouseDown", "layout"]);
30
31
  var focusRef = useFocusTrap(true);
@@ -46,11 +47,12 @@ var Layer = React.forwardRef(function (_a, ref) {
46
47
  document.removeEventListener("keydown", handleEscape);
47
48
  };
48
49
  }, [onClose, returnFocusOnEscape]);
50
+ var mergedRef = useMergeRefs(focusRef, children.props.ref);
49
51
  return (_jsx(Box, __assign({ layout: __assign({ axis: "y", align: "center", justify: "center" }, layout) }, rest, { className: classNames("aui-layer-backdrop", className), onMouseDown: function (e) {
50
52
  onMouseDown === null || onMouseDown === void 0 ? void 0 : onMouseDown(e);
51
53
  onClose === null || onClose === void 0 ? void 0 : onClose();
52
54
  }, ref: ref, children: React.cloneElement(children, {
53
- ref: focusRef,
55
+ ref: mergedRef,
54
56
  onMouseDown: function (e) {
55
57
  var _a, _b;
56
58
  e.stopPropagation();
@@ -28,7 +28,7 @@ import Button, { IconButton } from "../Button";
28
28
  import Animated from "../Animated";
29
29
  var Modal = React.forwardRef(function (_a, ref) {
30
30
  var open = _a.open, onClose = _a.onClose, onConfirm = _a.onConfirm, _b = _a.confirmLabel, confirmLabel = _b === void 0 ? "Ok" : _b, _c = _a.cancelLabel, cancelLabel = _c === void 0 ? "Cancel" : _c, returnFocusOnEscape = _a.returnFocusOnEscape, rest = __rest(_a, ["open", "onClose", "onConfirm", "confirmLabel", "cancelLabel", "returnFocusOnEscape"]);
31
- return (_jsx(Animated, { className: "aui-modal-backdrop", animated: open, animateTo: { style: { opacity: 1 } }, animateFrom: { style: { opacity: 0 } }, children: _jsx(Layer, { onClose: onClose, returnFocusOnEscape: returnFocusOnEscape, children: _jsxs(Box, { role: "dialog", "aria-modal": "true", className: "aui-modal aui-corners--rounded", layout: { axis: "y", padding: "m", gap: "m" }, children: [_jsx(Box, { layout: {
31
+ return (_jsx(Animated, { className: "aui-modal-backdrop", visible: open, animateTo: { style: { opacity: 1 } }, animateFrom: { style: { opacity: 0 } }, children: _jsx(Layer, { onClose: onClose, returnFocusOnEscape: returnFocusOnEscape, children: _jsxs(Box, { role: "dialog", "aria-modal": "true", className: "aui-modal aui-corners--rounded", layout: { axis: "y", padding: "m", gap: "m" }, children: [_jsx(Box, { layout: {
32
32
  axis: "x",
33
33
  align: "center",
34
34
  justify: "end",
package/hooks/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export { default as useScroll } from "./useScroll";
4
4
  export { default as useFocusTrap } from "./useFocusTrap";
5
5
  export { default as useScrollToHash } from "./useScrollToHash";
6
6
  export { default as useWindowResize } from "./useWindowResize";
7
+ export { default as useMergeRefs } from "./useMergeRefs";
package/hooks/index.js CHANGED
@@ -4,3 +4,4 @@ export { default as useScroll } from "./useScroll";
4
4
  export { default as useFocusTrap } from "./useFocusTrap";
5
5
  export { default as useScrollToHash } from "./useScrollToHash";
6
6
  export { default as useWindowResize } from "./useWindowResize";
7
+ export { default as useMergeRefs } from "./useMergeRefs";
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ /**
3
+ * Simple hook that merges N refs into one callback
4
+ *
5
+ * @param refs all the refs you want to merge
6
+ * @returns one combined ref
7
+ */
8
+ declare const useMergeRefs: <T>(...refs: (React.Ref<T> | null | undefined)[]) => React.Ref<T>;
9
+ export default useMergeRefs;
@@ -0,0 +1,24 @@
1
+ import { useCallback } from "react";
2
+ /**
3
+ * Simple hook that merges N refs into one callback
4
+ *
5
+ * @param refs all the refs you want to merge
6
+ * @returns one combined ref
7
+ */
8
+ var useMergeRefs = function () {
9
+ var refs = [];
10
+ for (var _i = 0; _i < arguments.length; _i++) {
11
+ refs[_i] = arguments[_i];
12
+ }
13
+ return useCallback(function (node) {
14
+ refs.forEach(function (ref) {
15
+ if (typeof ref === "function") {
16
+ ref(node);
17
+ }
18
+ else if (ref && typeof ref === "object") {
19
+ ref.current = node;
20
+ }
21
+ });
22
+ }, [refs]);
23
+ };
24
+ export default useMergeRefs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adamjanicki/ui",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
4
4
  "description": "Basic UI components and hooks for React in TypeScript",
5
5
  "type": "module",
6
6
  "main": "./index.js",