reshaped 3.0.7 → 3.0.8-rc.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.
@@ -39,7 +39,7 @@ const ModalSubtitle = (props) => {
39
39
  return (_jsx(Text, { variant: "body-3", color: "neutral-faded", attributes: { id: `${id}-subtitle` }, children: children }));
40
40
  };
41
41
  const Modal = (props) => {
42
- const { children, onClose, onOpen, active, size, padding = 4, position = "center", transparentOverlay, ariaLabel, autoFocus = true, disableSwipeGesture, overlayClassName, containerRef, className, attributes, } = props;
42
+ const { children, onClose, onOpen, active, size, padding = 4, position = "center", transparentOverlay, ariaLabel, autoFocus = true, disableSwipeGesture, overlayClassName, className, attributes, } = props;
43
43
  const id = useElementId();
44
44
  const clientPosition = useResponsiveClientValue(position);
45
45
  const [titleMounted, setTitleMounted] = React.useState(false);
@@ -164,7 +164,7 @@ const Modal = (props) => {
164
164
  setHideProgress(progress / 2);
165
165
  dragDistanceRef.current = dragDistance;
166
166
  }, [dragDistance, clientPosition, rootRef]);
167
- return (_jsx(Overlay, { onClose: onClose, onOpen: onOpen, active: active, transparent: transparentOverlay || hideProgress, className: overlayClassName, containerRef: containerRef, attributes: {
167
+ return (_jsx(Overlay, { onClose: onClose, onOpen: onOpen, active: active, transparent: transparentOverlay || hideProgress, className: overlayClassName, attributes: {
168
168
  onTouchStart: handleDragStart,
169
169
  }, children: ({ active }) => {
170
170
  const rootClassNames = classNames(s.root, className, paddingStyles?.classNames, active && s["--active"], dragging && s["--dragging"], responsiveClassNames(s, "--position", position));
@@ -28,4 +28,4 @@ export type Props = {
28
28
  attributes?: G.Attributes<"div"> & {
29
29
  ref?: React.RefObject<HTMLDivElement | null>;
30
30
  };
31
- } & Pick<OverlayProps, "onClose" | "onOpen" | "active" | "containerRef">;
31
+ } & Pick<OverlayProps, "onClose" | "onOpen" | "active">;
@@ -19,6 +19,5 @@ export declare const size: () => React.JSX.Element;
19
19
  export declare const padding: () => React.JSX.Element;
20
20
  export declare const composition: () => React.JSX.Element;
21
21
  export declare const overlay: () => React.JSX.Element;
22
- export declare const customContainer: () => React.JSX.Element;
23
22
  export declare const edgeCases: () => React.JSX.Element;
24
23
  export declare const trapFocusEdgeCases: () => React.JSX.Element;
@@ -1,5 +1,4 @@
1
1
  import React from "react";
2
- import { createRoot } from "react-dom/client";
3
2
  import { Example } from "../../../utilities/storybook/index.js";
4
3
  import Modal from "../index.js";
5
4
  import View from "../../View/index.js";
@@ -8,7 +7,6 @@ import Dismissible from "../../Dismissible/index.js";
8
7
  import DropdownMenu from "../../DropdownMenu/index.js";
9
8
  import Switch from "../../Switch/index.js";
10
9
  import TextField from "../../TextField/index.js";
11
- import Reshaped from "../../Reshaped/index.js";
12
10
  import useToggle from "../../../hooks/useToggle.js";
13
11
  import Radio from "../../Radio/index.js";
14
12
  export default {
@@ -102,40 +100,6 @@ export const overlay = () => (<Example>
102
100
  <View height="1000px"/>
103
101
  </Example.Item>
104
102
  </Example>);
105
- export const customContainer = () => {
106
- const toggle = useToggle();
107
- const containerRef = React.useRef(null);
108
- const shadowRootRef = React.useRef(null);
109
- React.useEffect(() => {
110
- if (!shadowRootRef.current)
111
- return;
112
- if (shadowRootRef.current.shadowRoot)
113
- return;
114
- const shadowRoot = shadowRootRef.current?.attachShadow({ mode: "open" });
115
- const root = createRoot(shadowRoot);
116
- root.render(<Reshaped theme="reshaped">
117
- <Modal active containerRef={{ current: shadowRootRef.current?.shadowRoot }}>
118
- Modal content
119
- </Modal>
120
- </Reshaped>);
121
- }, []);
122
- return (<Example>
123
- <Example.Item title="inside an element">
124
- <View backgroundColor="neutral-faded" height="400px" borderRadius="medium" attributes={{ ref: containerRef }} padding={4} overflow="auto">
125
- <View height="5000px">
126
- <Button onClick={toggle.activate}>Open</Button>
127
- </View>
128
- </View>
129
- <Modal onClose={toggle.deactivate} active={toggle.active} containerRef={containerRef} position="end">
130
- Modal content
131
- </Modal>
132
- </Example.Item>
133
-
134
- <Example.Item title="shadow DOM">
135
- <div ref={shadowRootRef} style={{ height: 400 }}/>
136
- </Example.Item>
137
- </Example>);
138
- };
139
103
  export const edgeCases = () => {
140
104
  const menuModalToggle = useToggle();
141
105
  const scrollModalToggle = useToggle();
@@ -12,14 +12,14 @@ import useIsDismissible from "../../hooks/_private/useIsDismissible.js";
12
12
  import Portal from "../_private/Portal/index.js";
13
13
  import s from "./Overlay.module.css";
14
14
  const Overlay = (props) => {
15
- const { active, children, transparent, onClose, onOpen, containerRef, className, attributes } = props;
16
- const clickThrough = transparent === true && !containerRef?.current;
17
- const opacity = transparent === true ? 0 : (1 - (transparent || 0)) * 0.7;
15
+ const { active, children, transparent, onClose, onOpen, className, attributes } = props;
16
+ const clickThrough = transparent === true;
17
+ const opacity = clickThrough ? 0 : (1 - (transparent || 0)) * 0.7;
18
18
  const [mounted, setMounted] = React.useState(false);
19
19
  const [animated, setAnimated] = React.useState(false);
20
20
  const contentRef = React.useRef(null);
21
21
  const isMouseDownValidRef = React.useRef(false);
22
- const { lockScroll, unlockScroll } = useScrollLock({ ref: containerRef });
22
+ const { lockScroll, unlockScroll } = useScrollLock();
23
23
  const { active: rendered, activate: render, deactivate: remove } = useToggle(active || false);
24
24
  const { active: visible, activate: show, deactivate: hide } = useToggle(active || false);
25
25
  const isDismissible = useIsDismissible(active, contentRef);
@@ -101,6 +101,6 @@ const Overlay = (props) => {
101
101
  }, []);
102
102
  if (!rendered || !mounted)
103
103
  return null;
104
- return (_jsx(Portal, { targetRef: containerRef, children: _jsx(Portal.Scope, { children: (ref) => (_jsx("div", { ...attributes, ref: ref, style: { "--rs-overlay-opacity": opacity }, role: "button", tabIndex: -1, className: rootClassNames, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onTransitionEnd: handleTransitionEnd, children: _jsx("div", { className: s.wrapper, children: _jsx("div", { className: s.inner, children: _jsx("div", { className: s.content, ref: contentRef, children: typeof children === "function" ? children({ active: visible }) : children }) }) }) })) }) }));
104
+ return (_jsx(Portal, { children: _jsx(Portal.Scope, { children: (ref) => (_jsx("div", { ...attributes, ref: ref, style: { "--rs-overlay-opacity": opacity }, role: "button", tabIndex: -1, className: rootClassNames, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, onTransitionEnd: handleTransitionEnd, children: _jsx("div", { className: s.wrapper, children: _jsx("div", { className: s.inner, children: _jsx("div", { className: s.content, ref: contentRef, children: typeof children === "function" ? children({ active: visible }) : children }) }) }) })) }) }));
105
105
  };
106
106
  export default Overlay;
@@ -1 +1 @@
1
- .root{overflow:auto;-webkit-overflow-scrolling:touch;background-color:rgba(var(--rs-color-rgb-black),0);color:var(--rs-color-white);cursor:default!important;inset:0;position:fixed;z-index:var(--rs-z-index-overlay)}.wrapper{display:table;height:100%;width:100%}.inner{display:table-cell;text-align:center}.content,.inner{vertical-align:middle}.content{display:inline-block;text-align:initial}.root.--visible{background-color:rgba(var(--rs-color-rgb-black),var(--rs-overlay-opacity))}.root.--click-through{color:inherit;pointer-events:none}.root.--click-through .content,.root.--click-through>:not(.wrapper){pointer-events:all}.root.--animated{transition:var(--rs-duration-medium) var(--rs-easing-accelerate);transition-property:background-color,transform}.root.--animated.--visible{transition-timing-function:var(--rs-easing-decelerate)}
1
+ .root{overflow:auto;-webkit-overflow-scrolling:touch;background-color:rgba(var(--rs-color-rgb-black),0);color:var(--rs-color-white);cursor:default!important;inset:0;opacity:0;position:fixed;z-index:var(--rs-z-index-overlay)}.wrapper{display:table;height:100%;width:100%}.inner{display:table-cell;text-align:center}.content,.inner{vertical-align:middle}.content{display:inline-block;text-align:initial}.root.--visible{background-color:rgba(var(--rs-color-rgb-black),var(--rs-overlay-opacity));opacity:1}.root.--click-through{color:inherit;pointer-events:none}.root.--click-through .content,.root.--click-through>:not(.wrapper){pointer-events:all}.root.--animated{transition:var(--rs-duration-medium) var(--rs-easing-accelerate);transition-property:background-color,transform,opacity}.root.--animated.--visible{transition-timing-function:var(--rs-easing-decelerate)}
@@ -8,7 +8,6 @@ export type Props = {
8
8
  active?: boolean;
9
9
  onClose?: () => void;
10
10
  onOpen?: () => void;
11
- containerRef?: React.RefObject<HTMLElement | ShadowRoot>;
12
11
  className?: G.ClassName;
13
12
  attributes?: G.Attributes<"div">;
14
13
  };
@@ -9,3 +9,4 @@ declare const _default: {
9
9
  };
10
10
  export default _default;
11
11
  export declare const base: () => import("react").JSX.Element;
12
+ export declare const shadowDom: () => import("react").JSX.Element;
@@ -1,6 +1,8 @@
1
+ import { createRoot } from "react-dom/client";
1
2
  import { Example } from "../../../utilities/storybook/index.js";
2
3
  import Overlay from "../index.js";
3
4
  import Button from "../../Button/index.js";
5
+ import Reshaped from "../../Reshaped/index.js";
4
6
  import useToggle from "../../../hooks/useToggle.js";
5
7
  export default {
6
8
  title: "Utilities/Overlay",
@@ -32,3 +34,25 @@ export const base = () => {
32
34
  </Example.Item>
33
35
  </Example>);
34
36
  };
37
+ class CustomElement extends window.HTMLElement {
38
+ constructor() {
39
+ super();
40
+ this.attachShadow({ mode: "open" });
41
+ if (!this.shadowRoot)
42
+ return;
43
+ const overlay = (<Reshaped>
44
+ <Overlay active>Content</Overlay>
45
+ </Reshaped>);
46
+ const root = createRoot(this.shadowRoot);
47
+ root.render(overlay);
48
+ }
49
+ }
50
+ window.customElements.define("custom-element", CustomElement);
51
+ export const shadowDom = () => {
52
+ return (<Example>
53
+ <Example.Item>
54
+ {/* @ts-ignore */}
55
+ <custom-element />
56
+ </Example.Item>
57
+ </Example>);
58
+ };
@@ -9,12 +9,14 @@ import useHotkeys from "../../../hooks/useHotkeys.js";
9
9
  import useOnClickOutside from "../../../hooks/_private/useOnClickOutside.js";
10
10
  import useRTL from "../../../hooks/useRTL.js";
11
11
  import { checkTransitions, onNextFrame } from "../../../utilities/animation.js";
12
+ import { checkKeyboardMode } from "../../../utilities/a11y/keyboardMode.js";
12
13
  import useFlyout from "./useFlyout.js";
13
14
  import * as timeouts from "./Flyout.constants.js";
14
15
  import cooldown from "./utilities/cooldown.js";
15
16
  import { Provider, useFlyoutTriggerContext, useFlyoutContext } from "./Flyout.context.js";
16
17
  const FlyoutRoot = (props) => {
17
18
  const { triggerType = "click", onOpen, onClose, children, disabled, forcePosition, trapFocusMode, width, disableHideAnimation, disableContentHover, contentGap, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, } = props;
19
+ const resolvedActive = disabled === true ? false : passedActive;
18
20
  const parentFlyoutContext = useFlyoutContext();
19
21
  const parentFlyoutTriggerContext = useFlyoutTriggerContext();
20
22
  const isSubmenu = parentFlyoutContext.trapFocusMode === "action-menu" ||
@@ -37,7 +39,7 @@ const FlyoutRoot = (props) => {
37
39
  const flyout = useFlyout(triggerElRef, flyoutElRef, {
38
40
  width,
39
41
  position: passedPosition,
40
- defaultActive: passedActive,
42
+ defaultActive: resolvedActive,
41
43
  container: containerRef?.current,
42
44
  forcePosition,
43
45
  });
@@ -53,15 +55,15 @@ const FlyoutRoot = (props) => {
53
55
  * Called from the internal actions
54
56
  */
55
57
  const handleOpen = React.useCallback(() => {
56
- const canOpen = !lockedRef.current && status === "idle" && !disabled;
58
+ const canOpen = !lockedRef.current && status === "idle";
57
59
  if (!canOpen)
58
60
  return;
59
61
  onOpen?.();
60
62
  // eslint-disable-next-line react-hooks/exhaustive-deps
61
- }, [status, disabled]);
63
+ }, [status]);
62
64
  const handleClose = React.useCallback((options) => {
63
65
  const isLocked = triggerType === "click" && !isDismissible();
64
- const canClose = !isLocked && status !== "idle" && !disabled;
66
+ const canClose = !isLocked && (status !== "idle" || disabled);
65
67
  if (!canClose)
66
68
  return;
67
69
  onClose?.();
@@ -69,11 +71,13 @@ const FlyoutRoot = (props) => {
69
71
  parentFlyoutContext?.handleClose?.();
70
72
  },
71
73
  // eslint-disable-next-line react-hooks/exhaustive-deps
72
- [status, isDismissible, triggerType, disabled]);
74
+ [status, isDismissible, triggerType]);
73
75
  /**
74
76
  * Trigger event handlers
75
77
  */
76
78
  const handleBlur = React.useCallback((e) => {
79
+ if (!checkKeyboardMode())
80
+ return;
77
81
  const focusedContent = flyoutElRef.current?.contains(e.relatedTarget);
78
82
  if (
79
83
  // Empty flyouts don't move the focus so they have to be closed on blur
@@ -87,6 +91,8 @@ const FlyoutRoot = (props) => {
87
91
  handleClose();
88
92
  }, [handleClose, triggerType, trapFocusMode]);
89
93
  const handleFocus = React.useCallback(() => {
94
+ if (!checkKeyboardMode())
95
+ return;
90
96
  handleOpen();
91
97
  }, [handleOpen]);
92
98
  const handleMouseEnter = React.useCallback(() => {
@@ -111,12 +117,12 @@ const FlyoutRoot = (props) => {
111
117
  const handleContentMouseDown = () => (lockedBlurEffects.current = true);
112
118
  const handleContentMouseUp = () => (lockedBlurEffects.current = false);
113
119
  const handleTransitionStart = React.useCallback((e) => {
114
- if (!passedActive)
120
+ if (!resolvedActive)
115
121
  return;
116
122
  if (flyoutElRef.current !== e.currentTarget || e.propertyName !== "transform")
117
123
  return;
118
124
  transitionStartedRef.current = true;
119
- }, [passedActive]);
125
+ }, [resolvedActive]);
120
126
  const handleTransitionEnd = React.useCallback((e) => {
121
127
  if (flyoutElRef.current !== e.currentTarget || e.propertyName !== "transform")
122
128
  return;
@@ -129,10 +135,12 @@ const FlyoutRoot = (props) => {
129
135
  * Control the display based on the props
130
136
  */
131
137
  useIsomorphicLayoutEffect(() => {
132
- if (passedActive) {
138
+ if (resolvedActive) {
133
139
  render();
134
140
  return;
135
141
  }
142
+ if (disabled)
143
+ cooldown.cool();
136
144
  /**
137
145
  * Check that transitions are enabled and it has been triggered on tooltip open
138
146
  * - keyboard focus navigation could move too fast and ignore the transitions completely
@@ -148,7 +156,7 @@ const FlyoutRoot = (props) => {
148
156
  // In case transitions are disabled globally - remove from the DOM immediately
149
157
  remove();
150
158
  }
151
- }, [passedActive, render, hide, remove, disableHideAnimation]);
159
+ }, [resolvedActive, render, hide, remove, disableHideAnimation, disabled]);
152
160
  React.useEffect(() => {
153
161
  // Wait after positioning before show is triggered to animate flyout from the right side
154
162
  if (status === "positioned")
@@ -11,7 +11,7 @@ class Cooldown {
11
11
  this.status = "cold";
12
12
  if (currentTimer === this.timer)
13
13
  this.timer = undefined;
14
- }, 1000);
14
+ }, 500);
15
15
  this.timer = currentTimer;
16
16
  };
17
17
  }
@@ -1,8 +1,9 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import ReactDOM from "react-dom";
5
5
  import Theme from "../../Theme/index.js";
6
+ import s from "./Portal.module.css";
6
7
  const PortalScopeContext = React.createContext({});
7
8
  export const usePortalScope = () => {
8
9
  return React.useContext(PortalScopeContext);
@@ -13,6 +14,10 @@ export const usePortalScope = () => {
13
14
  */
14
15
  const Portal = (props) => {
15
16
  const { children, targetRef } = props;
17
+ const rootRef = React.useRef(null);
18
+ const rootNode = rootRef.current?.getRootNode();
19
+ const isShadowDom = rootNode instanceof ShadowRoot;
20
+ const defaultTargetEl = isShadowDom ? rootNode : document.body;
16
21
  /**
17
22
  * Check for parent portal to render inside it
18
23
  * To avoid z-iondex issues
@@ -22,8 +27,9 @@ const Portal = (props) => {
22
27
  */
23
28
  const portal = usePortalScope();
24
29
  const nextScopeRef = targetRef || portal.scopeRef;
30
+ const targetEl = nextScopeRef?.current || defaultTargetEl;
25
31
  /* Preserve the current theme when rendered in body */
26
- return ReactDOM.createPortal(_jsx(Theme, { children: children }), nextScopeRef?.current || document.body);
32
+ return (_jsxs(_Fragment, { children: [ReactDOM.createPortal(_jsx(Theme, { children: children }), targetEl), _jsx("div", { ref: rootRef, className: s.root })] }));
27
33
  };
28
34
  function PortalScope(props) {
29
35
  const { children } = props;
@@ -0,0 +1 @@
1
+ .root{display:contents}
@@ -1,7 +1,4 @@
1
- import React from "react";
2
- declare const useScrollLock: (args?: {
3
- ref?: React.RefObject<HTMLElement | ShadowRoot>;
4
- }) => {
1
+ declare const useScrollLock: () => {
5
2
  scrollLocked: boolean;
6
3
  lockScroll: () => void;
7
4
  unlockScroll: () => void;
@@ -17,16 +17,11 @@ const getScrollbarWidth = (() => {
17
17
  return scrollbarWidth;
18
18
  };
19
19
  })();
20
- const useScrollLock = (args) => {
21
- const { ref } = args || {};
20
+ const useScrollLock = () => {
22
21
  const [locked, setLocked] = React.useState(false);
23
22
  const overflowStyleRef = React.useRef();
24
23
  const isOverflowingRef = React.useRef(false);
25
- let targetEl = document.body;
26
- if (ref?.current) {
27
- targetEl =
28
- ref?.current instanceof ShadowRoot ? ref?.current?.host : ref.current;
29
- }
24
+ const targetEl = document.body;
30
25
  const lockScroll = React.useCallback(() => {
31
26
  const rect = targetEl.getBoundingClientRect();
32
27
  isOverflowingRef.current = rect.left + rect.right < window.innerWidth;
@@ -112,9 +112,8 @@ class TrapFocus {
112
112
  if (!this.trapped || !this.chainId)
113
113
  return;
114
114
  this.trapped = false;
115
- if (this.trigger) {
116
- const preventScroll = withoutFocusReturn || !checkKeyboardMode();
117
- this.trigger.focus({ preventScroll });
115
+ if (this.trigger && !withoutFocusReturn) {
116
+ this.trigger.focus({ preventScroll: !checkKeyboardMode() });
118
117
  }
119
118
  TrapFocus.chain.removePreviousTill(this.chainId, (item) => document.body.contains(item.data.trigger));
120
119
  this.mutationObserver?.disconnect();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reshaped",
3
3
  "description": "Professionally crafted design system in React & Figma for building products of any scale and complexity",
4
- "version": "3.0.7",
4
+ "version": "3.0.8-rc.1",
5
5
  "license": "MIT",
6
6
  "email": "hello@reshaped.so",
7
7
  "homepage": "https://reshaped.so",