reshaped 3.2.4 → 3.2.6-rc.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/bundle.css +1 -1
  3. package/dist/bundle.js +11 -11
  4. package/dist/components/Actionable/Actionable.js +5 -1
  5. package/dist/components/Actionable/Actionable.types.d.ts +1 -0
  6. package/dist/components/Button/Button.js +2 -2
  7. package/dist/components/Button/Button.types.d.ts +1 -1
  8. package/dist/components/DropdownMenu/DropdownMenu.js +1 -0
  9. package/dist/components/DropdownMenu/DropdownMenu.types.d.ts +1 -1
  10. package/dist/components/Link/Link.d.ts +1 -1
  11. package/dist/components/Link/Link.js +2 -2
  12. package/dist/components/Link/Link.types.d.ts +1 -1
  13. package/dist/components/Link/tests/Link.stories.d.ts +1 -1
  14. package/dist/components/MenuItem/MenuItem.js +2 -2
  15. package/dist/components/MenuItem/MenuItem.types.d.ts +1 -1
  16. package/dist/components/Modal/Modal.module.css +1 -1
  17. package/dist/components/Popover/Popover.types.d.ts +1 -1
  18. package/dist/components/Reshaped/Reshaped.js +5 -1
  19. package/dist/components/Tabs/TabsList.js +7 -16
  20. package/dist/components/Theme/GlobalColorMode.js +11 -6
  21. package/dist/components/Theme/Theme.context.js +2 -12
  22. package/dist/components/Theme/Theme.js +6 -6
  23. package/dist/components/Theme/Theme.types.d.ts +3 -2
  24. package/dist/components/Theme/Theme.utilities.d.ts +1 -1
  25. package/dist/components/Theme/Theme.utilities.js +10 -1
  26. package/dist/components/Theme/tests/Theme.stories.d.ts +1 -0
  27. package/dist/components/Theme/tests/Theme.stories.js +34 -1
  28. package/dist/components/Theme/useTheme.d.ts +1 -1
  29. package/dist/components/Theme/useTheme.js +1 -2
  30. package/dist/components/Tooltip/Tooltip.js +2 -2
  31. package/dist/components/Tooltip/Tooltip.types.d.ts +1 -1
  32. package/dist/components/_private/Flyout/Flyout.types.d.ts +3 -1
  33. package/dist/components/_private/Flyout/FlyoutControlled.js +2 -1
  34. package/dist/components/_private/Flyout/tests/Flyout.stories.d.ts +4 -4
  35. package/dist/components/_private/Flyout/tests/Flyout.stories.js +26 -19
  36. package/dist/components/_private/Flyout/useFlyout.d.ts +1 -0
  37. package/dist/components/_private/Flyout/useFlyout.js +7 -4
  38. package/dist/components/_private/Flyout/utilities/calculatePosition.js +7 -7
  39. package/dist/hooks/_private/useSingletonHotkeys.d.ts +3 -3
  40. package/dist/hooks/_private/useSingletonHotkeys.js +22 -16
  41. package/dist/hooks/tests/useHotkeys.stories.js +5 -2
  42. package/dist/hooks/useHotkeys.d.ts +2 -1
  43. package/package.json +1 -1
@@ -7,7 +7,6 @@ import Theme from "../../../Theme/index.js";
7
7
  import Button from "../../../Button/index.js";
8
8
  import Flyout from "../index.js";
9
9
  import TextField from "../../../TextField/index.js";
10
- import useToggle from "../../../../hooks/useToggle.js";
11
10
  export default { title: "Utilities/Internal/Flyout" };
12
11
  const Demo = (props) => {
13
12
  const { position = "bottom-start", children, ...rest } = props;
@@ -20,7 +19,7 @@ const Demo = (props) => {
20
19
  background: "var(--rs-color-background-elevation-overlay)",
21
20
  padding: "var(--rs-unit-x4)",
22
21
  height: 150,
23
- width: 160,
22
+ minWidth: 160,
24
23
  borderRadius: "var(--rs-radius-medium)",
25
24
  border: "1px solid var(--rs-color-border-neutral-faded)",
26
25
  boxSizing: "border-box",
@@ -30,7 +29,7 @@ const Demo = (props) => {
30
29
  </Flyout.Content>
31
30
  </Flyout>);
32
31
  };
33
- export const positions = () => (<div style={{ paddingTop: 200 }}>
32
+ export const position = () => (<div style={{ paddingTop: 200 }}>
34
33
  <View gap={3} direction="row">
35
34
  <Demo position="bottom-start"/>
36
35
  <Demo position="bottom-end"/>
@@ -107,20 +106,35 @@ export const modes = () => (<Example>
107
106
  </Demo>
108
107
  </Example.Item>
109
108
  </Example>);
110
- export const widthTrigger = () => (<Flyout triggerType="click" width="trigger" position="bottom">
111
- <Flyout.Trigger>
112
- {(attributes) => <button {...attributes}>Trigger with long text</button>}
113
- </Flyout.Trigger>
114
- <Flyout.Content>
115
- <div style={{
109
+ export const width = () => (<Example>
110
+ <Example.Item title="width: 300px">
111
+ <Demo width="300px" position="bottom"/>
112
+ </Example.Item>
113
+ <Example.Item title="width: trigger">
114
+ <Flyout triggerType="click" width="trigger" position="bottom">
115
+ <Flyout.Trigger>
116
+ {(attributes) => <Button attributes={attributes}>Trigger with long text</Button>}
117
+ </Flyout.Trigger>
118
+ <Flyout.Content>
119
+ <div style={{
116
120
  background: "var(--rs-color-background-elevation-overlay)",
117
121
  padding: "var(--rs-unit-x4)",
118
122
  borderRadius: "var(--rs-radius-medium)",
119
123
  border: "1px solid var(--rs-color-border-neutral-faded)",
120
124
  boxSizing: "border-box",
121
125
  }}></div>
122
- </Flyout.Content>
123
- </Flyout>);
126
+ </Flyout.Content>
127
+ </Flyout>
128
+ </Example.Item>
129
+ </Example>);
130
+ export const offset = () => (<Example>
131
+ <Example.Item title="contentGap: x10">
132
+ <Demo contentGap={10}/>
133
+ </Example.Item>
134
+ <Example.Item title="contentShift: x10">
135
+ <Demo contentShift={10}/>
136
+ </Example.Item>
137
+ </Example>);
124
138
  export const disableFlags = () => (<Example>
125
139
  <Example.Item title="disableContentHover">
126
140
  <Demo triggerType="hover" disableContentHover>
@@ -198,12 +212,6 @@ export const customPortalTarget = () => {
198
212
  </Example.Item>
199
213
  </Example>);
200
214
  };
201
- export const testWidthOverflowOnMobile = () => (<Demo position="bottom-start" width={600}>
202
- Should work on mobile
203
- <button type="button">Item 1</button>
204
- <button type="button">Item 2</button>
205
- <button type="button">Close</button>
206
- </Demo>);
207
215
  export const testInsideFixed = () => (<Example>
208
216
  <Example.Item title="should move the content on page scroll">
209
217
  <View position="fixed" insetTop={20} insetStart={0} insetEnd={0} backgroundColor="neutral-faded" padding={4}>
@@ -277,7 +285,6 @@ export const testDynamicBounds = () => {
277
285
  </View>);
278
286
  };
279
287
  export const testDisableOutsideClick = () => {
280
- const toggle = useToggle();
281
288
  return (<Example>
282
289
  <Example.Item title="opening second flyout shouldn't block the first one from closing">
283
290
  <View direction="row" gap={4}>
@@ -287,7 +294,7 @@ export const testDisableOutsideClick = () => {
287
294
  </Example.Item>
288
295
  </Example>);
289
296
  };
290
- export const scopedTheming = () => (<View gap={3} align="start">
297
+ export const testScopedTheming = () => (<View gap={3} align="start">
291
298
  <Button color="primary">Reshaped button</Button>
292
299
  <Theme name="twitter">
293
300
  <Flyout triggerType="click" active position="bottom-start">
@@ -16,6 +16,7 @@ type UseFlyout = (args: PassedFlyoutOptions & {
16
16
  flyoutElRef: ElementRef;
17
17
  triggerBoundsRef: React.RefObject<DOMRect | undefined>;
18
18
  contentGap?: number;
19
+ contentShift?: number;
19
20
  }) => Pick<T.State, "styles" | "position" | "status"> & {
20
21
  updatePosition: (options?: {
21
22
  sync?: boolean;
@@ -43,12 +43,12 @@ const resetStyles = {
43
43
  * Set position of the target element to fit on the screen
44
44
  */
45
45
  const flyout = (args) => {
46
- const { triggerEl, flyoutEl, triggerBounds: passedTriggerBounds, contentGap = 0, ...options } = args;
46
+ const { triggerEl, flyoutEl, triggerBounds: passedTriggerBounds, contentShift = 0, contentGap = 0, ...options } = args;
47
47
  const { position, fallbackPositions, width, container, lastUsedFallback, onFallback } = options;
48
48
  const targetClone = flyoutEl.cloneNode(true);
49
49
  const triggerBounds = passedTriggerBounds || triggerEl.getBoundingClientRect();
50
50
  const baseUnit = getComputedStyle(flyoutEl).getPropertyValue("--rs-unit-x1");
51
- const contentGapModifier = baseUnit ? parseInt(baseUnit) : 0;
51
+ const unitModifier = baseUnit ? parseInt(baseUnit) : 0;
52
52
  // Reset all styles applied on the previous hook execution
53
53
  targetClone.style.cssText = "";
54
54
  Object.keys(resetStyles).forEach((key) => {
@@ -83,7 +83,8 @@ const flyout = (args) => {
83
83
  flyoutBounds,
84
84
  scopeOffset,
85
85
  position: currentPosition,
86
- contentGap: contentGap * contentGapModifier,
86
+ contentGap: contentGap * unitModifier,
87
+ contentShift: contentShift * unitModifier,
87
88
  });
88
89
  const visible = fullyVisible(tested);
89
90
  const validPosition = visible || fallbackPositions?.length === 0;
@@ -135,7 +136,7 @@ const flyoutReducer = (state, action) => {
135
136
  }
136
137
  };
137
138
  const useFlyout = (args) => {
138
- const { triggerElRef, flyoutElRef, triggerBoundsRef, contentGap, ...options } = args;
139
+ const { triggerElRef, flyoutElRef, triggerBoundsRef, contentGap, contentShift, ...options } = args;
139
140
  const { position: defaultPosition = "bottom", fallbackPositions, width, container } = options;
140
141
  const lastUsedFallbackRef = React.useRef(defaultPosition);
141
142
  // Memo the array internally to avoid new arrays triggering useCallback
@@ -178,6 +179,7 @@ const useFlyout = (args) => {
178
179
  rtl: isRTL,
179
180
  container,
180
181
  contentGap,
182
+ contentShift,
181
183
  });
182
184
  if (nextFlyoutData)
183
185
  dispatch({ type: "position", payload: { ...nextFlyoutData, sync: options?.sync } });
@@ -191,6 +193,7 @@ const useFlyout = (args) => {
191
193
  triggerBoundsRef,
192
194
  width,
193
195
  contentGap,
196
+ contentShift,
194
197
  handleFallback,
195
198
  ]);
196
199
  React.useEffect(() => {
@@ -16,7 +16,7 @@ const centerBySize = (originSize, targetSize) => {
16
16
  * Calculate styles for the current position
17
17
  */
18
18
  const calculatePosition = (args) => {
19
- const { triggerBounds, flyoutBounds, scopeOffset, position: passedPosition, rtl, width, contentGap = 0, } = args;
19
+ const { triggerBounds, flyoutBounds, scopeOffset, position: passedPosition, rtl, width, contentGap = 0, contentShift = 0, } = args;
20
20
  const isFullWidth = width === "full" || width === "100%";
21
21
  let left = 0;
22
22
  let top = 0;
@@ -33,7 +33,7 @@ const calculatePosition = (args) => {
33
33
  switch (position) {
34
34
  case "bottom":
35
35
  case "top":
36
- left = centerBySize(triggerBounds.width, flyoutWidth) + triggerBounds.left;
36
+ left = centerBySize(triggerBounds.width, flyoutWidth) + triggerBounds.left + contentShift;
37
37
  break;
38
38
  case "start":
39
39
  case "start-top":
@@ -47,11 +47,11 @@ const calculatePosition = (args) => {
47
47
  break;
48
48
  case "top-start":
49
49
  case "bottom-start":
50
- left = triggerBounds.left;
50
+ left = triggerBounds.left + contentShift + contentShift;
51
51
  break;
52
52
  case "top-end":
53
53
  case "bottom-end":
54
- left = triggerBounds.right - flyoutWidth;
54
+ left = triggerBounds.right - flyoutWidth + contentShift;
55
55
  break;
56
56
  default:
57
57
  break;
@@ -69,15 +69,15 @@ const calculatePosition = (args) => {
69
69
  break;
70
70
  case "start":
71
71
  case "end":
72
- top = centerBySize(triggerBounds.height, flyoutHeight) + triggerBounds.top;
72
+ top = centerBySize(triggerBounds.height, flyoutHeight) + triggerBounds.top + contentShift;
73
73
  break;
74
74
  case "start-top":
75
75
  case "end-top":
76
- top = triggerBounds.top;
76
+ top = triggerBounds.top + contentShift;
77
77
  break;
78
78
  case "start-bottom":
79
79
  case "end-bottom":
80
- top = triggerBounds.bottom - flyoutHeight;
80
+ top = triggerBounds.bottom - flyoutHeight + contentShift;
81
81
  break;
82
82
  default:
83
83
  break;
@@ -2,9 +2,9 @@ import React from "react";
2
2
  /**
3
3
  * Types
4
4
  */
5
- type Callback = (e: KeyboardEvent) => void;
6
- type PressedMap = Record<string, KeyboardEvent>;
7
- type Hotkeys = Record<string, Callback | null>;
5
+ type Callback = (e?: KeyboardEvent) => void;
6
+ type PressedMap = Map<string, KeyboardEvent>;
7
+ export type Hotkeys = Record<string, Callback | null>;
8
8
  type HotkeyOptions = {
9
9
  preventDefault?: boolean;
10
10
  };
@@ -4,7 +4,7 @@ import React from "react";
4
4
  * Utilities
5
5
  */
6
6
  const COMBINATION_DELIMETER = "+";
7
- const pressedMap = {};
7
+ const pressedMap = new Map();
8
8
  let modifiedKeys = [];
9
9
  const formatHotkey = (hotkey) => {
10
10
  if (hotkey === " ")
@@ -62,9 +62,9 @@ export class HotkeyStore {
62
62
  });
63
63
  };
64
64
  handleKeyDown = (pressedMap, e) => {
65
- const pressedKeys = Object.keys(pressedMap);
66
- if (!pressedKeys.length)
65
+ if (!pressedMap.size)
67
66
  return;
67
+ const pressedKeys = [...pressedMap.keys()];
68
68
  const pressedId = getHotkeyId(pressedKeys.join(COMBINATION_DELIMETER));
69
69
  const pressedFormattedKeys = pressedId.split(COMBINATION_DELIMETER);
70
70
  const hotkeyData = this.hotkeyMap[pressedId];
@@ -86,12 +86,12 @@ export class HotkeyStore {
86
86
  !(eventTarget === data.ref.current || data.ref.current.contains(eventTarget))) {
87
87
  return;
88
88
  }
89
- const resolvedEvent = pressedMap[pressedId];
89
+ const resolvedEvent = pressedMap.get(pressedId);
90
90
  if (data.options.preventDefault) {
91
91
  resolvedEvent?.preventDefault();
92
92
  e.preventDefault();
93
93
  }
94
- data.callback(resolvedEvent);
94
+ data.callback(e);
95
95
  });
96
96
  }
97
97
  });
@@ -114,13 +114,13 @@ export const SingletonHotkeysProvider = (props) => {
114
114
  const eventKey = getEventKey(e);
115
115
  if (!eventKey)
116
116
  return;
117
- pressedMap[eventKey] = e;
118
- setTriggerCount(Object.keys(pressedMap).length);
117
+ pressedMap.set(eventKey, e);
118
+ setTriggerCount(pressedMap.size);
119
119
  // Key up won't trigger for other keys while Meta is pressed so we need to cache them
120
120
  // and remove on Meta keyup
121
121
  if (e.metaKey)
122
- modifiedKeys.push(...Object.keys(pressedMap));
123
- if (pressedMap.Meta)
122
+ modifiedKeys.push(...pressedMap.keys());
123
+ if (pressedMap.has("Meta"))
124
124
  modifiedKeys.push(eventKey);
125
125
  }, [hooksCount]);
126
126
  const removePressedKey = React.useCallback((e) => {
@@ -129,23 +129,23 @@ export const SingletonHotkeysProvider = (props) => {
129
129
  const eventKey = getEventKey(e);
130
130
  if (!eventKey)
131
131
  return;
132
- delete pressedMap[eventKey];
132
+ pressedMap.delete(eventKey);
133
133
  if (eventKey === "meta" || eventKey === "control") {
134
- delete pressedMap.mod;
134
+ pressedMap.delete("mod");
135
135
  }
136
136
  if (eventKey === "meta") {
137
137
  modifiedKeys.forEach((key) => {
138
- if (!pressedMap[key])
138
+ if (!pressedMap.has(key))
139
139
  return;
140
- delete pressedMap[key];
140
+ pressedMap.delete(key);
141
141
  });
142
142
  modifiedKeys = [];
143
143
  }
144
- setTriggerCount(Object.keys(pressedMap).length);
144
+ setTriggerCount(pressedMap.size);
145
145
  }, [hooksCount]);
146
146
  const isPressed = (hotkey) => {
147
147
  const keys = formatHotkey(hotkey).split(COMBINATION_DELIMETER);
148
- if (keys.some((key) => !pressedMap[key]))
148
+ if (keys.some((key) => !pressedMap.has(key)))
149
149
  return false;
150
150
  return true;
151
151
  };
@@ -161,6 +161,10 @@ export const SingletonHotkeysProvider = (props) => {
161
161
  return;
162
162
  removePressedKey(e);
163
163
  }, [removePressedKey]);
164
+ const handleWindowBlur = React.useCallback(() => {
165
+ pressedMap.clear();
166
+ modifiedKeys = [];
167
+ }, []);
164
168
  const addHotkeys = React.useCallback((hotkeys, ref, options = {}) => {
165
169
  setHooksCount((prev) => prev + 1);
166
170
  globalHotkeyStore.bindHotkeys(hotkeys, ref, options);
@@ -172,11 +176,13 @@ export const SingletonHotkeysProvider = (props) => {
172
176
  React.useEffect(() => {
173
177
  window.addEventListener("keydown", handleWindowKeyDown);
174
178
  window.addEventListener("keyup", handleWindowKeyUp);
179
+ window.addEventListener("blur", handleWindowBlur);
175
180
  return () => {
176
181
  window.removeEventListener("keydown", handleWindowKeyDown);
177
182
  window.removeEventListener("keyup", handleWindowKeyUp);
183
+ window.removeEventListener("blur", handleWindowBlur);
178
184
  };
179
- }, [handleWindowKeyDown, handleWindowKeyUp]);
185
+ }, [handleWindowKeyDown, handleWindowKeyUp, handleWindowBlur]);
180
186
  return (_jsx(HotkeyContext.Provider, { value: { addHotkeys, isPressed }, children: children }));
181
187
  };
182
188
  const useSingletonHotkeys = () => React.useContext(HotkeyContext);
@@ -4,8 +4,11 @@ export default { title: "Hooks/useHotkeys" };
4
4
  function Example() {
5
5
  const { checkHotkeyState } = useHotkeys({
6
6
  "shift + b + n": () => console.log("pressed"),
7
- "c + v": () => console.log(111),
8
- "Meta + v": () => console.log(222),
7
+ "c + v": () => console.log("c + v"),
8
+ "Meta + k": () => console.log("meta + k"),
9
+ "Meta + f": () => console.log("meta + f"),
10
+ "Meta + v": () => console.log("meta + v"),
11
+ "Meta + b": () => console.log("meta + b"),
9
12
  "control + enter": () => console.log("control + enter"),
10
13
  "meta + enter": () => console.log("meta + enter"),
11
14
  "mod + enter": () => console.log("mod + enter"),
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
- declare const useHotkeys: <Element extends HTMLElement>(hotkeys: Record<string, ((e: KeyboardEvent) => void) | null>, deps?: unknown[], options?: {
2
+ import { type Hotkeys } from "./_private/useSingletonHotkeys";
3
+ declare const useHotkeys: <Element extends HTMLElement>(hotkeys: Hotkeys, deps?: unknown[], options?: {
3
4
  ref?: React.RefObject<Element>;
4
5
  disabled?: boolean;
5
6
  preventDefault?: boolean;
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.2.4",
4
+ "version": "3.2.6-rc.0",
5
5
  "license": "MIT",
6
6
  "email": "hello@reshaped.so",
7
7
  "homepage": "https://reshaped.so",