reshaped 3.1.0 → 3.1.2

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.
@@ -40,7 +40,7 @@ const ModalSubtitle = (props) => {
40
40
  return (_jsx(Text, { variant: "body-3", color: "neutral-faded", attributes: { id: `${id}-subtitle` }, children: children }));
41
41
  };
42
42
  const Modal = (props) => {
43
- const { children, onClose, onOpen, active, size, padding = 4, position = "center", transparentOverlay, ariaLabel, autoFocus = true, disableSwipeGesture, overlayClassName, className, attributes, } = props;
43
+ const { children, onClose, onOpen, active, size, padding = 4, position = "center", transparentOverlay, blurredOverlay, ariaLabel, autoFocus = true, disableSwipeGesture, disableCloseOnOutsideClick, overlayClassName, className, attributes, } = props;
44
44
  const onCloseRef = useHandlerRef(onClose);
45
45
  const id = useElementId();
46
46
  const clientPosition = useResponsiveClientValue(position);
@@ -165,7 +165,7 @@ const Modal = (props) => {
165
165
  setHideProgress(progress / 2);
166
166
  dragDistanceRef.current = dragDistance;
167
167
  }, [dragDistance, clientPosition, rootRef]);
168
- return (_jsx(Overlay, { onClose: onClose, onOpen: onOpen, active: active, transparent: transparentOverlay || hideProgress, className: overlayClassName, attributes: {
168
+ return (_jsx(Overlay, { onClose: onClose, onOpen: onOpen, disableCloseOnClick: disableCloseOnOutsideClick, active: active, transparent: transparentOverlay || hideProgress, blurred: blurredOverlay, className: overlayClassName, attributes: {
169
169
  onTouchStart: handleDragStart,
170
170
  }, children: ({ active }) => {
171
171
  const rootClassNames = classNames(s.root, className, paddingStyles?.classNames, active && s["--active"], dragging && s["--dragging"], responsiveClassNames(s, "--position", position));
@@ -20,7 +20,9 @@ export type Props = {
20
20
  size?: G.Responsive<string>;
21
21
  padding?: G.Responsive<number>;
22
22
  transparentOverlay?: boolean;
23
+ blurredOverlay?: boolean;
23
24
  disableSwipeGesture?: boolean;
25
+ disableCloseOnOutsideClick?: boolean;
24
26
  autoFocus?: boolean;
25
27
  ariaLabel?: string;
26
28
  className?: G.ClassName;
@@ -19,5 +19,6 @@ 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 flags: () => React.JSX.Element;
22
23
  export declare const edgeCases: () => React.JSX.Element;
23
24
  export declare const trapFocusEdgeCases: () => React.JSX.Element;
@@ -97,9 +97,19 @@ export const composition = () => (<Example>
97
97
  export const overlay = () => (<Example>
98
98
  <Example.Item title="transparentOverlay, doesn't lock scroll">
99
99
  <Demo transparentOverlay/>
100
- <View height="1000px"/>
101
100
  </Example.Item>
101
+ <Example.Item title="blurredOverlay">
102
+ <Demo blurredOverlay/>
103
+ </Example.Item>
104
+ <View height="1000px"/>
102
105
  </Example>);
106
+ export const flags = () => {
107
+ return (<Example>
108
+ <Example.Item title="disableCloseOnOutsideClick">
109
+ <Demo disableCloseOnOutsideClick/>
110
+ </Example.Item>
111
+ </Example>);
112
+ };
103
113
  export const edgeCases = () => {
104
114
  const menuModalToggle = useToggle();
105
115
  const scrollModalToggle = useToggle();
@@ -13,7 +13,7 @@ import Portal from "../_private/Portal/index.js";
13
13
  import s from "./Overlay.module.css";
14
14
  import useHandlerRef from "../../hooks/useHandlerRef.js";
15
15
  const Overlay = (props) => {
16
- const { active, children, transparent, onClose, onOpen, className, attributes } = props;
16
+ const { active, children, transparent, blurred, onClose, onOpen, disableCloseOnClick, className, attributes, } = props;
17
17
  const onCloseRef = useHandlerRef(onClose);
18
18
  const onOpenRef = useHandlerRef(onOpen);
19
19
  const clickThrough = transparent === true;
@@ -26,7 +26,7 @@ const Overlay = (props) => {
26
26
  const { active: rendered, activate: render, deactivate: remove } = useToggle(active || false);
27
27
  const { active: visible, activate: show, deactivate: hide } = useToggle(active || false);
28
28
  const isDismissible = useIsDismissible(active, contentRef);
29
- const rootClassNames = classNames(s.root, visible && s["--visible"], clickThrough && s["--click-through"], animated && s["--animated"], className);
29
+ const rootClassNames = classNames(s.root, visible && s["--visible"], clickThrough && s["--click-through"], blurred && s["--blurred"], animated && s["--animated"], className);
30
30
  const isInsideChild = (el) => {
31
31
  if (!contentRef.current)
32
32
  return;
@@ -46,7 +46,7 @@ const Overlay = (props) => {
46
46
  const handleMouseUp = (event) => {
47
47
  const isMouseUpValid = !isInsideChild(event.target);
48
48
  const shouldClose = isMouseDownValidRef.current && isMouseUpValid && !clickThrough;
49
- if (!shouldClose)
49
+ if (!shouldClose || disableCloseOnClick)
50
50
  return;
51
51
  close();
52
52
  };
@@ -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;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
+ .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:-1px;opacity:0;outline:none;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.--blurred{backdrop-filter:blur(3px)}.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)}
@@ -2,12 +2,14 @@ import type React from "react";
2
2
  import type * as G from "../../types/global";
3
3
  export type Props = {
4
4
  transparent?: boolean | number;
5
+ blurred?: boolean;
5
6
  children?: React.ReactNode | ((props: {
6
7
  active: boolean;
7
8
  }) => React.ReactNode);
8
9
  active?: boolean;
9
10
  onClose?: () => void;
10
11
  onOpen?: () => void;
12
+ disableCloseOnClick?: boolean;
11
13
  className?: G.ClassName;
12
14
  attributes?: G.Attributes<"div">;
13
15
  };
@@ -14,15 +14,15 @@ export default {
14
14
  },
15
15
  };
16
16
  export const base = () => {
17
- const baseToggle = useToggle(true);
17
+ const baseToggle = useToggle(false);
18
18
  const transparentToggle = useToggle(false);
19
+ const blurredToggle = useToggle(false);
19
20
  return (<Example>
20
21
  <Example.Item title="locks scroll">
21
22
  <Button onClick={() => baseToggle.activate()}>Open overlay</Button>
22
23
  <Overlay active={baseToggle.active} onClose={() => baseToggle.deactivate()}>
23
24
  Overlay content
24
25
  </Overlay>
25
- <div style={{ height: 1000 }}/>
26
26
  </Example.Item>
27
27
 
28
28
  <Example.Item title="transparent, doesn't lock scroll">
@@ -30,8 +30,16 @@ export const base = () => {
30
30
  <Overlay active={transparentToggle.active} onClose={() => transparentToggle.deactivate()} transparent>
31
31
  Overlay content
32
32
  </Overlay>
33
- <div style={{ height: 1000 }}/>
34
33
  </Example.Item>
34
+
35
+ <Example.Item title="blurred">
36
+ <Button onClick={() => blurredToggle.activate()}>Open overlay</Button>
37
+ <Overlay active={blurredToggle.active} onClose={() => blurredToggle.deactivate()} blurred>
38
+ Overlay content
39
+ </Overlay>
40
+ </Example.Item>
41
+
42
+ <div style={{ height: 1000 }}/>
35
43
  </Example>);
36
44
  };
37
45
  class CustomElement extends window.HTMLElement {
@@ -54,5 +54,6 @@ export const base = () => (<Example>
54
54
  </Resizable>
55
55
  </Resizable.Item>
56
56
  </Resizable>
57
+ <div style={{ height: 2000 }}/>
57
58
  </Example.Item>
58
59
  </Example>);
@@ -123,33 +123,34 @@ export const edgeCases = () => (<Example>
123
123
  </Example.Item>
124
124
 
125
125
  <Example.Item title="nested popovers inside a tooltip">
126
- <Tooltip position="top" text="Hello">
127
- {(tooltipAttributes) => (<Popover position="bottom">
128
- <Popover.Trigger>
129
- {(attributes) => (<Button color="primary" attributes={{
130
- ...tooltipAttributes,
131
- ...attributes,
132
- }}>
133
- Open
134
- </Button>)}
135
- </Popover.Trigger>
136
- <Popover.Content>
137
- <View gap={2} align="start">
138
- Popover content
139
- <Popover position="bottom">
140
- <Popover.Trigger>
141
- {(attributes) => <Button attributes={attributes}>Open</Button>}
142
- </Popover.Trigger>
143
- <Popover.Content>
144
- <View gap={2} align="start">
145
- Popover content
146
- <Button onClick={() => { }}>Button</Button>
147
- </View>
148
- </Popover.Content>
149
- </Popover>
150
- </View>
151
- </Popover.Content>
152
- </Popover>)}
153
- </Tooltip>
126
+ <View direction="row" gap={2}>
127
+ <Tooltip position="top" text="Hello">
128
+ {(tooltipAttributes) => (<Popover position="bottom" width="300px">
129
+ <Popover.Trigger>
130
+ {(attributes) => (<Button attributes={{ ...tooltipAttributes, ...attributes }}>
131
+ Tooltip with popover
132
+ </Button>)}
133
+ </Popover.Trigger>
134
+ <Popover.Content>
135
+ <View gap={2} align="center" direction="row" justify="space-between">
136
+ Popover content
137
+ <Popover position="bottom" width="300px">
138
+ <Popover.Trigger>
139
+ {(attributes) => <Button attributes={attributes}>Open</Button>}
140
+ </Popover.Trigger>
141
+ <Popover.Content>
142
+ <Popover.Dismissible align="center" closeAriaLabel="Close">
143
+ Another popover content
144
+ </Popover.Dismissible>
145
+ </Popover.Content>
146
+ </Popover>
147
+ </View>
148
+ </Popover.Content>
149
+ </Popover>)}
150
+ </Tooltip>
151
+ <Tooltip position="top" text="Hello">
152
+ {(tooltipAttributes) => <Button attributes={tooltipAttributes}>Just a tooltip</Button>}
153
+ </Tooltip>
154
+ </View>
154
155
  </Example.Item>
155
156
  </Example>);
@@ -1,3 +1,3 @@
1
1
  export declare const mouseEnter = 800;
2
- export declare const mouseEnterShort = 50;
2
+ export declare const mouseEnterShort = 100;
3
3
  export declare const mouseLeave = 150;
@@ -1,3 +1,3 @@
1
1
  export const mouseEnter = 800;
2
- export const mouseEnterShort = 50;
2
+ export const mouseEnterShort = 100;
3
3
  export const mouseLeave = 150;
@@ -52,6 +52,7 @@ export type TriggerAttributes = {
52
52
  onFocus?: () => void;
53
53
  onMouseEnter?: () => void;
54
54
  onMouseLeave?: () => void;
55
+ onTouchStart?: () => void;
55
56
  onClick?: () => void;
56
57
  "aria-describedby"?: string;
57
58
  "aria-haspopup"?: "dialog" | "menu" | "listbox";
@@ -111,6 +112,7 @@ export type ContextProps = {
111
112
  handleClick: () => void;
112
113
  handleBlur: (e: React.FocusEvent) => void;
113
114
  handleFocus: () => void;
115
+ handleTouchStart: () => void;
114
116
  handleContentMouseDown: () => void;
115
117
  handleContentMouseUp: () => void;
116
118
  isSubmenu: boolean;
@@ -38,9 +38,16 @@ const FlyoutRoot = (props) => {
38
38
  const timerRef = React.useRef();
39
39
  const trapFocusRef = React.useRef(null);
40
40
  const lockedRef = React.useRef(false);
41
+ // Check if transition had enough time to start when opening a flyout
42
+ // In some cases there is not enough time to start, like when you're holding tab key
41
43
  const transitionStartedRef = React.useRef(false);
44
+ // Lock blur event while pressing anywhere inside the flyout content
42
45
  const lockedBlurEffects = React.useRef(false);
46
+ // Focus shouldn't retrun back to the trigger when user intentionally clicks outside the flyout
43
47
  const shouldReturnFocusRef = React.useRef(true);
48
+ // Touch devices trigger onMouseEnter but we don't need to apply regular hover timeouts
49
+ // So we're saving a flag on touch start and then change the mouse enter behavior
50
+ const hoverTriggeredWithTouchEventRef = React.useRef(false);
44
51
  const flyout = useFlyout(triggerElRef, flyoutElRef, {
45
52
  width,
46
53
  position: passedPosition,
@@ -92,6 +99,11 @@ const FlyoutRoot = (props) => {
92
99
  }
93
100
  handleClose();
94
101
  }, [handleClose, triggerType, trapFocusMode]);
102
+ const handleTouchStart = React.useCallback(() => {
103
+ if (triggerType !== "hover")
104
+ return;
105
+ hoverTriggeredWithTouchEventRef.current = true;
106
+ }, [triggerType]);
95
107
  const handleFocus = React.useCallback(() => {
96
108
  if (triggerType === "hover" && !checkKeyboardMode())
97
109
  return;
@@ -99,9 +111,15 @@ const FlyoutRoot = (props) => {
99
111
  }, [handleOpen, triggerType]);
100
112
  const handleMouseEnter = React.useCallback(() => {
101
113
  clearTimer();
102
- timerRef.current = setTimeout(handleOpen, cooldown.timer || isSubmenu ? timeouts.mouseEnterShort : timeouts.mouseEnter);
103
- if (!isSubmenu && triggerType === "hover")
104
- cooldown.warm();
114
+ if (hoverTriggeredWithTouchEventRef.current) {
115
+ handleOpen();
116
+ hoverTriggeredWithTouchEventRef.current = false;
117
+ }
118
+ else {
119
+ timerRef.current = setTimeout(handleOpen, cooldown.timer || isSubmenu ? timeouts.mouseEnterShort : timeouts.mouseEnter);
120
+ if (!isSubmenu && triggerType === "hover")
121
+ cooldown.warm();
122
+ }
105
123
  }, [clearTimer, timerRef, handleOpen, isSubmenu, triggerType]);
106
124
  const handleMouseLeave = React.useCallback(() => {
107
125
  cooldown.cool();
@@ -116,8 +134,13 @@ const FlyoutRoot = (props) => {
116
134
  handleClose();
117
135
  }
118
136
  }, [status, handleOpen, handleClose]);
119
- const handleContentMouseDown = () => (lockedBlurEffects.current = true);
120
- const handleContentMouseUp = () => (lockedBlurEffects.current = false);
137
+ const handleContentMouseDown = () => {
138
+ lockedBlurEffects.current = true;
139
+ hoverTriggeredWithTouchEventRef.current = true;
140
+ };
141
+ const handleContentMouseUp = () => {
142
+ lockedBlurEffects.current = false;
143
+ };
121
144
  const handleTransitionStart = React.useCallback((e) => {
122
145
  if (!resolvedActive)
123
146
  return;
@@ -251,6 +274,7 @@ const FlyoutRoot = (props) => {
251
274
  handleBlur,
252
275
  handleMouseEnter,
253
276
  handleMouseLeave,
277
+ handleTouchStart,
254
278
  handleTransitionStart,
255
279
  handleTransitionEnd,
256
280
  handleClick: handleTriggerClick,
@@ -3,9 +3,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useFlyoutContext, TriggerProvider } from "./Flyout.context.js";
4
4
  const FlyoutTrigger = (props) => {
5
5
  const { children } = props;
6
- const { id, triggerElRef, triggerType, flyout, handleFocus, handleBlur, handleMouseEnter, handleMouseLeave, handleClick, trapFocusMode, isSubmenu, } = useFlyoutContext();
6
+ const { id, triggerElRef, triggerType, flyout, handleFocus, handleBlur, handleMouseEnter, handleMouseLeave, handleTouchStart, handleClick, trapFocusMode, isSubmenu, } = useFlyoutContext();
7
7
  let childrenAttributes = {
8
- onBlur: handleBlur,
9
8
  ref: triggerElRef,
10
9
  };
11
10
  if (triggerType === "click" || trapFocusMode === "action-menu") {
@@ -14,10 +13,12 @@ const FlyoutTrigger = (props) => {
14
13
  if (triggerType === "hover") {
15
14
  childrenAttributes.onMouseEnter = handleMouseEnter;
16
15
  childrenAttributes.onMouseLeave = handleMouseLeave;
16
+ childrenAttributes.onTouchStart = handleTouchStart;
17
17
  }
18
18
  // Submenus open on keypress instead of hover
19
19
  if ((triggerType === "hover" && !isSubmenu) || triggerType === "focus") {
20
20
  childrenAttributes.onFocus = handleFocus;
21
+ childrenAttributes.onBlur = handleBlur;
21
22
  childrenAttributes["aria-describedby"] = id;
22
23
  }
23
24
  if (triggerType === "click" || triggerType === "focus" || trapFocusMode === "action-menu") {
@@ -86,6 +86,7 @@ export class HotkeyStore {
86
86
  const resolvedEvent = pressedMap[pressedId];
87
87
  if (data.options.preventDefault) {
88
88
  resolvedEvent?.preventDefault();
89
+ e.preventDefault();
89
90
  }
90
91
  data.callback(resolvedEvent);
91
92
  });
@@ -42,6 +42,7 @@ const useDrag = (cb, options) => {
42
42
  [keys.DOWN]: () => isVertical && handleKeyboard(0, 20),
43
43
  }, [], {
44
44
  ref: triggerRef,
45
+ preventDefault: true,
45
46
  disabled,
46
47
  });
47
48
  React.useEffect(() => {
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.1.0",
4
+ "version": "3.1.2",
5
5
  "license": "MIT",
6
6
  "email": "hello@reshaped.so",
7
7
  "homepage": "https://reshaped.so",