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.
- package/CHANGELOG.md +2 -2
- package/dist/bundle.css +1 -1
- package/dist/bundle.js +10 -10
- package/dist/components/Modal/Modal.js +2 -2
- package/dist/components/Modal/Modal.types.d.ts +1 -1
- package/dist/components/Modal/tests/Modal.stories.d.ts +0 -1
- package/dist/components/Modal/tests/Modal.stories.js +0 -36
- package/dist/components/Overlay/Overlay.js +5 -5
- package/dist/components/Overlay/Overlay.module.css +1 -1
- package/dist/components/Overlay/Overlay.types.d.ts +0 -1
- package/dist/components/Overlay/tests/Overlay.stories.d.ts +1 -0
- package/dist/components/Overlay/tests/Overlay.stories.js +24 -0
- package/dist/components/_private/Flyout/FlyoutControlled.js +17 -9
- package/dist/components/_private/Flyout/utilities/cooldown.js +1 -1
- package/dist/components/_private/Portal/Portal.js +8 -2
- package/dist/components/_private/Portal/Portal.module.css +1 -0
- package/dist/hooks/useScrollLock.d.ts +1 -4
- package/dist/hooks/useScrollLock.js +2 -7
- package/dist/utilities/a11y/TrapFocus.js +2 -3
- package/package.json +1 -1
@@ -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,
|
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,
|
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));
|
@@ -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,
|
16
|
-
const clickThrough = transparent === true
|
17
|
-
const opacity =
|
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(
|
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, {
|
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)}
|
@@ -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:
|
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"
|
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
|
63
|
+
}, [status]);
|
62
64
|
const handleClose = React.useCallback((options) => {
|
63
65
|
const isLocked = triggerType === "click" && !isDismissible();
|
64
|
-
const canClose = !isLocked && status !== "idle"
|
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
|
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 (!
|
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
|
-
}, [
|
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 (
|
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
|
-
}, [
|
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")
|
@@ -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 }),
|
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}
|
@@ -17,16 +17,11 @@ const getScrollbarWidth = (() => {
|
|
17
17
|
return scrollbarWidth;
|
18
18
|
};
|
19
19
|
})();
|
20
|
-
const useScrollLock = (
|
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
|
-
|
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
|
-
|
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.
|
4
|
+
"version": "3.0.8-rc.1",
|
5
5
|
"license": "MIT",
|
6
6
|
"email": "hello@reshaped.so",
|
7
7
|
"homepage": "https://reshaped.so",
|